Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 61741dd

Browse files
author
David Chung
authored
Handshake early to detect plugin version mismatch (#369)
Signed-off-by: David Chung <[email protected]>
1 parent 21dc4cd commit 61741dd

File tree

19 files changed

+163
-77
lines changed

19 files changed

+163
-77
lines changed

cmd/cli/flavor.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ func flavorPluginCommand(plugins func() discovery.Plugins) *cobra.Command {
3434
return err
3535
}
3636

37-
flavorPlugin = flavor_plugin.NewClient(plugin.Name(*name), endpoint.Address)
38-
37+
p, err := flavor_plugin.NewClient(plugin.Name(*name), endpoint.Address)
38+
if err != nil {
39+
return err
40+
}
41+
flavorPlugin = p
3942
return nil
4043
}
4144

cmd/cli/group.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ func groupPluginCommand(plugins func() discovery.Plugins) *cobra.Command {
3737
return err
3838
}
3939

40-
groupPlugin = group_plugin.NewClient(endpoint.Address)
41-
40+
p, err := group_plugin.NewClient(endpoint.Address)
41+
if err != nil {
42+
return err
43+
}
44+
groupPlugin = p
4245
return nil
4346
}
4447

cmd/cli/instance.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ func instancePluginCommand(plugins func() discovery.Plugins) *cobra.Command {
3232
return err
3333
}
3434

35-
instancePlugin = instance_plugin.NewClient(plugin.Name(*name), endpoint.Address)
36-
35+
p, err := instance_plugin.NewClient(plugin.Name(*name), endpoint.Address)
36+
if err != nil {
37+
return err
38+
}
39+
instancePlugin = p
3740
return nil
3841
}
3942

cmd/group/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ func main() {
4141
if err != nil {
4242
return nil, err
4343
}
44-
return instance_client.NewClient(n, endpoint.Address), nil
44+
return instance_client.NewClient(n, endpoint.Address)
4545
}
4646

4747
flavorPluginLookup := func(n plugin.Name) (flavor.Plugin, error) {
4848
endpoint, err := plugins.Find(n)
4949
if err != nil {
5050
return nil, err
5151
}
52-
return flavor_client.NewClient(n, endpoint.Address), nil
52+
return flavor_client.NewClient(n, endpoint.Address)
5353
}
5454

5555
cli.RunPlugin(*name, group_server.PluginServer(

examples/flavor/combo/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func main() {
3333
if err != nil {
3434
return nil, err
3535
}
36-
return flavor_rpc.NewClient(n, endpoint.Address), nil
36+
return flavor_rpc.NewClient(n, endpoint.Address)
3737
}
3838

3939
cli.SetLogLevel(*logLevel)

pkg/manager/group_plugin_impl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func (m *manager) proxyForGroupPlugin(name string) (group.Plugin, error) {
2222
if err != nil {
2323
return nil, err
2424
}
25-
return rpc.NewClient(endpoint.Address), nil
25+
return rpc.NewClient(endpoint.Address)
2626
}), nil
2727
}
2828

pkg/manager/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func (m *manager) execPlugins(config GlobalSpec, work func(group.Plugin, group.S
332332
return err
333333
}
334334

335-
gp := rpc.NewClient(ep.Address)
335+
gp, err := rpc.NewClient(ep.Address)
336336
if err != nil {
337337
log.Warningln("Cannot contact group", id, " at plugin", name, "endpoint=", ep.Address)
338338
return err

pkg/rpc/client/client.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ type client struct {
1616
}
1717

1818
// New creates a new Client that communicates with a unix socket and validates the remote API.
19-
func New(socketPath string, api spi.InterfaceSpec) Client {
19+
func New(socketPath string, api spi.InterfaceSpec) (Client, error) {
2020
dialUnix := func(proto, addr string) (conn net.Conn, err error) {
2121
return net.Dial("unix", socketPath)
2222
}
2323

2424
unvalidatedClient := &client{http: http.Client{Transport: &http.Transport{Dial: dialUnix}}}
25-
return &handshakingClient{client: unvalidatedClient, iface: api, lock: &sync.Mutex{}}
25+
cl := &handshakingClient{client: unvalidatedClient, iface: api, lock: &sync.Mutex{}}
26+
27+
// check handshake
28+
if err := cl.handshake(); err != nil {
29+
// Note - we still return the client with the possibility of doing a handshake later on
30+
// if we provide an api for the plugin to recheck later. This way, individual components
31+
// can stay running and recalibrate themselves after the user has corrected the problems.
32+
return cl, err
33+
}
34+
return cl, nil
2635
}
2736

2837
func (c client) Call(method string, arg interface{}, result interface{}) error {

pkg/rpc/client/handshake.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ type handshakeResult struct {
2525
err error
2626
}
2727

28+
type errVersionMismatch string
29+
30+
// Error implements error interface
31+
func (e errVersionMismatch) Error() string {
32+
return string(e)
33+
}
34+
35+
// IsErrVersionMismatch return true if the error is from mismatched api versions.
36+
func IsErrVersionMismatch(e error) bool {
37+
_, is := e.(errVersionMismatch)
38+
return is
39+
}
40+
2841
func (c *handshakingClient) handshake() error {
2942
c.lock.Lock()
3043
defer c.lock.Unlock()
@@ -45,11 +58,11 @@ func (c *handshakingClient) handshake() error {
4558
err = nil
4659
break
4760
} else {
48-
err = fmt.Errorf(
61+
err = errVersionMismatch(fmt.Sprintf(
4962
"Plugin supports %s interface version %s, client requires %s",
5063
iface.Name,
5164
iface.Version,
52-
c.iface.Version)
65+
c.iface.Version))
5366
}
5467
}
5568
}

pkg/rpc/client/handshake_test.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package client
22

33
import (
4-
"github.com/docker/infrakit/pkg/rpc/server"
5-
"github.com/docker/infrakit/pkg/spi"
6-
"github.com/stretchr/testify/require"
4+
"fmt"
75
"io/ioutil"
86
"net/http"
97
"path/filepath"
108
"testing"
9+
10+
"github.com/docker/infrakit/pkg/rpc/server"
11+
"github.com/docker/infrakit/pkg/spi"
12+
"github.com/stretchr/testify/require"
1113
)
1214

1315
var apiSpec = spi.InterfaceSpec{
@@ -27,20 +29,35 @@ func startPluginServer(t *testing.T) (server.Stoppable, string) {
2729
return testServer, socket
2830
}
2931

32+
func TestErrVersionMismatch(t *testing.T) {
33+
var e error
34+
35+
e = errVersionMismatch("test")
36+
require.True(t, IsErrVersionMismatch(e))
37+
38+
e = fmt.Errorf("untyped")
39+
require.False(t, IsErrVersionMismatch(e))
40+
}
41+
3042
func TestHandshakeSuccess(t *testing.T) {
3143
testServer, socket := startPluginServer(t)
3244
defer testServer.Stop()
3345

34-
client := rpcClient{client: New(socket, apiSpec)}
46+
r, err := New(socket, apiSpec)
47+
require.NoError(t, err)
48+
client := rpcClient{client: r}
3549
require.NoError(t, client.DoSomething())
3650
}
3751

3852
func TestHandshakeFailVersion(t *testing.T) {
3953
testServer, socket := startPluginServer(t)
4054
defer testServer.Stop()
4155

42-
client := rpcClient{client: New(socket, spi.InterfaceSpec{Name: "TestPlugin", Version: "0.2.0"})}
43-
err := client.DoSomething()
56+
r, err := New(socket, spi.InterfaceSpec{Name: "TestPlugin", Version: "0.2.0"})
57+
require.Error(t, err)
58+
59+
client := rpcClient{client: r}
60+
err = client.DoSomething()
4461
require.Error(t, err)
4562
require.Equal(t, "Plugin supports TestPlugin interface version 0.1.0, client requires 0.2.0", err.Error())
4663
}
@@ -49,8 +66,11 @@ func TestHandshakeFailWrongAPI(t *testing.T) {
4966
testServer, socket := startPluginServer(t)
5067
defer testServer.Stop()
5168

52-
client := rpcClient{client: New(socket, spi.InterfaceSpec{Name: "OtherPlugin", Version: "0.1.0"})}
53-
err := client.DoSomething()
69+
r, err := New(socket, spi.InterfaceSpec{Name: "OtherPlugin", Version: "0.1.0"})
70+
require.Error(t, err)
71+
72+
client := rpcClient{client: r}
73+
err = client.DoSomething()
5474
require.Error(t, err)
5575
require.Equal(t, "Plugin does not support interface {OtherPlugin 0.1.0}", err.Error())
5676
}

0 commit comments

Comments
 (0)