Implement DeleteItemAction plugin support (#2808)

* Add DeleteItemAction struct & protobuf definition

Signed-off-by: Nolan Brubaker <brubakern@vmware.com>
This commit is contained in:
Nolan Brubaker
2020-08-18 15:16:26 -04:00
committed by GitHub
parent d1a1d063e1
commit e9ece0f7b5
22 changed files with 1119 additions and 72 deletions

View File

@@ -0,0 +1,44 @@
/*
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 framework
import (
plugin "github.com/hashicorp/go-plugin"
"golang.org/x/net/context"
"google.golang.org/grpc"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
)
// RestoreItemActionPlugin is an implementation of go-plugin's Plugin
// interface with support for gRPC for the restore/ItemAction
// interface.
type DeleteItemActionPlugin struct {
plugin.NetRPCUnsupportedPlugin
*pluginBase
}
// GRPCClient returns a RestoreItemAction gRPC client.
func (p *DeleteItemActionPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) {
return newClientDispenser(p.clientLogger, clientConn, newDeleteItemActionGRPCClient), nil
}
// GRPCServer registers a DeleteItemAction gRPC server.
func (p *DeleteItemActionPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error {
proto.RegisterDeleteItemActionServer(server, &DeleteItemActionGRPCServer{mux: p.serverMux})
return nil
}

View File

@@ -0,0 +1,95 @@
/*
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 framework
import (
"encoding/json"
"github.com/pkg/errors"
"golang.org/x/net/context"
"google.golang.org/grpc"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
var _ velero.DeleteItemAction = &DeleteItemActionGRPCClient{}
// NewDeleteItemActionPlugin constructs a DeleteItemActionPlugin.
func NewDeleteItemActionPlugin(options ...PluginOption) *DeleteItemActionPlugin {
return &DeleteItemActionPlugin{
pluginBase: newPluginBase(options...),
}
}
// DeleteItemActionGRPCClient implements the backup/ItemAction interface and uses a
// gRPC client to make calls to the plugin server.
type DeleteItemActionGRPCClient struct {
*clientBase
grpcClient proto.DeleteItemActionClient
}
func newDeleteItemActionGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
return &DeleteItemActionGRPCClient{
clientBase: base,
grpcClient: proto.NewDeleteItemActionClient(clientConn),
}
}
func (c *DeleteItemActionGRPCClient) AppliesTo() (velero.ResourceSelector, error) {
res, err := c.grpcClient.AppliesTo(context.Background(), &proto.DeleteItemActionAppliesToRequest{Plugin: c.plugin})
if err != nil {
return velero.ResourceSelector{}, fromGRPCError(err)
}
if res.ResourceSelector == nil {
return velero.ResourceSelector{}, nil
}
return velero.ResourceSelector{
IncludedNamespaces: res.ResourceSelector.IncludedNamespaces,
ExcludedNamespaces: res.ResourceSelector.ExcludedNamespaces,
IncludedResources: res.ResourceSelector.IncludedResources,
ExcludedResources: res.ResourceSelector.ExcludedResources,
LabelSelector: res.ResourceSelector.Selector,
}, nil
}
func (c *DeleteItemActionGRPCClient) Execute(input *velero.DeleteItemActionExecuteInput) error {
itemJSON, err := json.Marshal(input.Item.UnstructuredContent())
if err != nil {
return errors.WithStack(err)
}
backupJSON, err := json.Marshal(input.Backup)
if err != nil {
return errors.WithStack(err)
}
req := &proto.DeleteItemActionExecuteRequest{
Plugin: c.plugin,
Item: itemJSON,
Backup: backupJSON,
}
// First return item is just an empty struct no matter what.
if _, err = c.grpcClient.Execute(context.Background(), req); err != nil {
return fromGRPCError(err)
}
return nil
}

View File

@@ -0,0 +1,112 @@
/*
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 framework
import (
"encoding/json"
"github.com/pkg/errors"
"golang.org/x/net/context"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// DeleteItemActionGRPCServer implements the proto-generated DeleteItemActionServer interface, and accepts
// gRPC calls and forwards them to an implementation of the pluggable interface.
type DeleteItemActionGRPCServer struct {
mux *serverMux
}
func (s *DeleteItemActionGRPCServer) getImpl(name string) (velero.DeleteItemAction, error) {
impl, err := s.mux.getHandler(name)
if err != nil {
return nil, err
}
itemAction, ok := impl.(velero.DeleteItemAction)
if !ok {
return nil, errors.Errorf("%T is not a delete item action", impl)
}
return itemAction, nil
}
func (s *DeleteItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.DeleteItemActionAppliesToRequest) (response *proto.DeleteItemActionAppliesToResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
resourceSelector, err := impl.AppliesTo()
if err != nil {
return nil, newGRPCError(err)
}
return &proto.DeleteItemActionAppliesToResponse{
&proto.ResourceSelector{
IncludedNamespaces: resourceSelector.IncludedNamespaces,
ExcludedNamespaces: resourceSelector.ExcludedNamespaces,
IncludedResources: resourceSelector.IncludedResources,
ExcludedResources: resourceSelector.ExcludedResources,
Selector: resourceSelector.LabelSelector,
},
}, nil
}
func (s *DeleteItemActionGRPCServer) Execute(ctx context.Context, req *proto.DeleteItemActionExecuteRequest) (_ *proto.Empty, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var (
item unstructured.Unstructured
backup api.Backup
)
if err := json.Unmarshal(req.Item, &item); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
if err = json.Unmarshal(req.Backup, &backup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
if err := impl.Execute(&velero.DeleteItemActionExecuteInput{
Item: &item,
Backup: &backup,
}); err != nil {
return nil, newGRPCError(err)
}
return nil, nil
}

View File

@@ -1,5 +1,5 @@
/*
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.
@@ -19,12 +19,15 @@ package framework
import (
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
func ExampleNewServer_volumeSnapshotter() {
NewServer(). // call the server
RegisterVolumeSnapshotter("example.io/volumesnapshotter", newVolumeSnapshotter). // register the plugin with a valid name
Serve() // serve the plugin
RegisterDeleteItemAction("example.io/delete-item-action", newDeleteItemAction).
Serve() // serve the plugin
}
func newVolumeSnapshotter(logger logrus.FieldLogger) (interface{}, error) {
@@ -91,3 +94,29 @@ func (b *VolumeSnapshotter) DeleteSnapshot(snapshotID string) error {
return nil
}
// Implement all methods for the DeleteItemAction interface
func newDeleteItemAction(logger logrus.FieldLogger) (interface{}, error) {
return DeleteItemAction{FieldLogger: logger}, nil
}
type DeleteItemAction struct {
FieldLogger logrus.FieldLogger
}
func (d *DeleteItemAction) AppliesTo() (velero.ResourceSelector, error) {
d.FieldLogger.Infof("AppliesTo called")
// ...
return velero.ResourceSelector{}, nil
}
func (d *DeleteItemAction) Execute(input *velero.DeleteItemActionExecuteInput) error {
d.FieldLogger.Infof("Execute called")
// ...
return nil
}

View File

@@ -38,6 +38,9 @@ const (
// PluginKindRestoreItemAction represents a restore item action plugin.
PluginKindRestoreItemAction PluginKind = "RestoreItemAction"
// PluginKindDeleteItemAction represents a delete item action plugin.
PluginKindDeleteItemAction PluginKind = "DeleteItemAction"
// PluginKindPluginLister represents a plugin lister plugin.
PluginKindPluginLister PluginKind = "PluginLister"
)
@@ -50,5 +53,6 @@ func AllPluginKinds() map[string]PluginKind {
allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter
allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction
allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction
allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction
return allPluginKinds
}

View File

@@ -67,6 +67,13 @@ type Server interface {
// RegisterRestoreItemActions registers multiple restore item actions.
RegisterRestoreItemActions(map[string]HandlerInitializer) Server
// RegisterDeleteItemAction registers a delete item action. Accepted format
// for the plugin name is <DNS subdomain>/<non-empty name>.
RegisterDeleteItemAction(pluginName string, initializer HandlerInitializer) Server
// RegisterDeleteItemActions registers multiple Delete item actions.
RegisterDeleteItemActions(map[string]HandlerInitializer) Server
// Server runs the plugin server.
Serve()
}
@@ -81,6 +88,7 @@ type server struct {
volumeSnapshotter *VolumeSnapshotterPlugin
objectStore *ObjectStorePlugin
restoreItemAction *RestoreItemActionPlugin
deleteItemAction *DeleteItemActionPlugin
}
// NewServer returns a new Server
@@ -96,6 +104,7 @@ func NewServer() Server {
volumeSnapshotter: NewVolumeSnapshotterPlugin(serverLogger(log)),
objectStore: NewObjectStorePlugin(serverLogger(log)),
restoreItemAction: NewRestoreItemActionPlugin(serverLogger(log)),
deleteItemAction: NewDeleteItemActionPlugin(serverLogger(log)),
}
}
@@ -156,6 +165,18 @@ func (s *server) RegisterRestoreItemActions(m map[string]HandlerInitializer) Ser
return s
}
func (s *server) RegisterDeleteItemAction(name string, initializer HandlerInitializer) Server {
s.deleteItemAction.register(name, initializer)
return s
}
func (s *server) RegisterDeleteItemActions(m map[string]HandlerInitializer) Server {
for name := range m {
s.RegisterDeleteItemAction(name, m[name])
}
return s
}
// getNames returns a list of PluginIdentifiers registered with plugin.
func getNames(command string, kind PluginKind, plugin Interface) []PluginIdentifier {
var pluginIdentifiers []PluginIdentifier