docs/libmachine/drivers/rpc/client_driver.go

309 lines
7.3 KiB
Go

package rpcdriver
import (
"fmt"
"net/rpc"
"time"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/state"
"github.com/docker/machine/libmachine/version"
)
var (
heartbeatInterval = 200 * time.Millisecond
)
type RPCClientDriver struct {
plugin localbinary.DriverPlugin
heartbeatDoneCh chan bool
Client *InternalClient
}
type RPCCall struct {
ServiceMethod string
Args interface{}
Reply interface{}
}
type InternalClient struct {
MachineName string
RPCClient *rpc.Client
}
func (ic *InternalClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
if serviceMethod != "RPCServerDriver.Heartbeat" {
log.Debugf("(%s) Calling %+v", ic.MachineName, serviceMethod)
}
return ic.RPCClient.Call(serviceMethod, args, reply)
}
func NewInternalClient(rpcclient *rpc.Client) *InternalClient {
return &InternalClient{
RPCClient: rpcclient,
}
}
func NewRPCClientDriver(rawDriverData []byte, driverName string) (*RPCClientDriver, error) {
mcnName := ""
p, err := localbinary.NewPlugin(driverName)
if err != nil {
return nil, err
}
go func() {
if err := p.Serve(); err != nil {
// TODO: Is this best approach?
log.Warn(err)
return
}
}()
addr, err := p.Address()
if err != nil {
return nil, fmt.Errorf("Error attempting to get plugin server address for RPC: %s", err)
}
rpcclient, err := rpc.DialHTTP("tcp", addr)
if err != nil {
return nil, err
}
c := &RPCClientDriver{
Client: NewInternalClient(rpcclient),
heartbeatDoneCh: make(chan bool),
}
go func(c *RPCClientDriver) {
for {
select {
case <-c.heartbeatDoneCh:
return
default:
if err := c.Client.Call("RPCServerDriver.Heartbeat", struct{}{}, nil); err != nil {
log.Warnf("Error attempting heartbeat call to plugin server: %s", err)
c.Close()
return
}
time.Sleep(heartbeatInterval)
}
}
}(c)
var serverVersion int
if err := c.Client.Call("RPCServerDriver.GetVersion", struct{}{}, &serverVersion); err != nil {
return nil, err
}
if serverVersion != version.APIVersion {
return nil, fmt.Errorf("Driver binary uses an incompatible API version (%d)", serverVersion)
}
log.Debug("Using API Version ", serverVersion)
if err := c.SetConfigRaw(rawDriverData); err != nil {
return nil, err
}
mcnName = c.GetMachineName()
p.MachineName = mcnName
c.Client.MachineName = mcnName
c.plugin = p
return c, nil
}
func (c *RPCClientDriver) MarshalJSON() ([]byte, error) {
return c.GetConfigRaw()
}
func (c *RPCClientDriver) UnmarshalJSON(data []byte) error {
return c.SetConfigRaw(data)
}
func (c *RPCClientDriver) Close() error {
c.heartbeatDoneCh <- true
close(c.heartbeatDoneCh)
log.Debug("Making call to close connection to plugin binary")
if err := c.plugin.Close(); err != nil {
return err
}
log.Debug("Making call to close driver server")
if err := c.Client.Call("RPCServerDriver.Close", struct{}{}, nil); err != nil {
return err
}
log.Debug("Successfully made call to close driver server")
return nil
}
// Helper method to make requests which take no arguments and return simply a
// string, e.g. "GetIP".
func (c *RPCClientDriver) rpcStringCall(method string) (string, error) {
var info string
if err := c.Client.Call(method, struct{}{}, &info); err != nil {
return "", err
}
return info, nil
}
func (c *RPCClientDriver) GetCreateFlags() []mcnflag.Flag {
var flags []mcnflag.Flag
if err := c.Client.Call("RPCServerDriver.GetCreateFlags", struct{}{}, &flags); err != nil {
log.Warnf("Error attempting call to get create flags: %s", err)
}
return flags
}
func (c *RPCClientDriver) SetConfigRaw(data []byte) error {
return c.Client.Call("RPCServerDriver.SetConfigRaw", data, nil)
}
func (c *RPCClientDriver) GetConfigRaw() ([]byte, error) {
var data []byte
if err := c.Client.Call("RPCServerDriver.GetConfigRaw", struct{}{}, &data); err != nil {
return nil, err
}
return data, nil
}
// DriverName returns the name of the driver
func (c *RPCClientDriver) DriverName() string {
driverName, err := c.rpcStringCall("RPCServerDriver.DriverName")
if err != nil {
log.Warnf("Error attempting call to get driver name: %s", err)
}
return driverName
}
func (c *RPCClientDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
return c.Client.Call("RPCServerDriver.SetConfigFromFlags", &flags, nil)
}
func (c *RPCClientDriver) GetURL() (string, error) {
return c.rpcStringCall("RPCServerDriver.GetURL")
}
func (c *RPCClientDriver) GetMachineName() string {
name, err := c.rpcStringCall("RPCServerDriver.GetMachineName")
if err != nil {
log.Warnf("Error attempting call to get machine name: %s", err)
}
return name
}
func (c *RPCClientDriver) GetIP() (string, error) {
return c.rpcStringCall("RPCServerDriver.GetIP")
}
func (c *RPCClientDriver) GetSSHHostname() (string, error) {
return c.rpcStringCall("RPCServerDriver.GetSSHHostname")
}
// GetSSHKeyPath returns the key path
// TODO: This method doesn't even make sense to have with RPC.
func (c *RPCClientDriver) GetSSHKeyPath() string {
path, err := c.rpcStringCall("RPCServerDriver.GetSSHKeyPath")
if err != nil {
log.Warnf("Error attempting call to get SSH key path: %s", err)
}
return path
}
func (c *RPCClientDriver) GetSSHPort() (int, error) {
var port int
if err := c.Client.Call("RPCServerDriver.GetSSHPort", struct{}{}, &port); err != nil {
return 0, err
}
return port, nil
}
func (c *RPCClientDriver) GetSSHUsername() string {
username, err := c.rpcStringCall("RPCServerDriver.GetSSHUsername")
if err != nil {
log.Warnf("Error attempting call to get SSH username: %s", err)
}
return username
}
func (c *RPCClientDriver) GetState() (state.State, error) {
var s state.State
if err := c.Client.Call("RPCServerDriver.GetState", struct{}{}, &s); err != nil {
return state.Error, err
}
return s, nil
}
func (c *RPCClientDriver) PreCreateCheck() error {
return c.Client.Call("RPCServerDriver.PreCreateCheck", struct{}{}, nil)
}
func (c *RPCClientDriver) Create() error {
return c.Client.Call("RPCServerDriver.Create", struct{}{}, nil)
}
func (c *RPCClientDriver) Remove() error {
return c.Client.Call("RPCServerDriver.Remove", struct{}{}, nil)
}
func (c *RPCClientDriver) Start() error {
return c.Client.Call("RPCServerDriver.Start", struct{}{}, nil)
}
func (c *RPCClientDriver) Stop() error {
return c.Client.Call("RPCServerDriver.Stop", struct{}{}, nil)
}
func (c *RPCClientDriver) Restart() error {
return c.Client.Call("RPCServerDriver.Restart", struct{}{}, nil)
}
func (c *RPCClientDriver) Kill() error {
return c.Client.Call("RPCServerDriver.Kill", struct{}{}, nil)
}
func (c *RPCClientDriver) LocalArtifactPath(file string) string {
var path string
if err := c.Client.Call("RPCServerDriver.LocalArtifactPath", file, &path); err != nil {
log.Warnf("Error attempting call to get LocalArtifactPath: %s", err)
}
return path
}
func (c *RPCClientDriver) GlobalArtifactPath() string {
globalArtifactPath, err := c.rpcStringCall("RPCServerDriver.GlobalArtifactPath")
if err != nil {
log.Warnf("Error attempting call to get GlobalArtifactPath: %s", err)
}
return globalArtifactPath
}
func (c *RPCClientDriver) Upgrade() error {
return c.Client.Call("RPCServerDriver.Upgrade", struct{}{}, nil)
}