Commit 2da59305 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #701 from mitchellh/rpc-refactor

RPC happens over single TCP connection per plugin now

Three benefits:
* Single file descriptor per plugin
* NAT-friendly since plugins don't have to dial back in to the host
* Opens the foundation that we can easily use Unix domain sockets and such

A handful of Packer users were having issues with highly parallel (many builder/provisioner) templates where their systems would quickly reach their default file descriptor limits. This was because the previous mechanism would use a single TCP connection per RPC server, and Packer needs many (one per interface, basically). 

This merges in a MuxConn that multiplexes many "streams" on top of a single io.ReadWriteCloser. The RPC system has been revamped to know about this and use unique stream IDs to send everything over a single connection per plugin.

Previously, the RPC mechanism would sometimes send an address to the remote end and expect the remote end to connect back to it. While Packer shouldn't run remotely, some firewalls were having issues. This should be gone.

Finally, it should be possible now to optimize and use Unix domain sockets on Unix systems, avoiding ports and firewalls altogether.
parents 958f4191 8a24c9b1
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/rpc"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
...@@ -130,56 +129,56 @@ func (c *Client) Exited() bool { ...@@ -130,56 +129,56 @@ func (c *Client) Exited() bool {
// Returns a builder implementation that is communicating over this // Returns a builder implementation that is communicating over this
// client. If the client hasn't been started, this will start it. // client. If the client hasn't been started, this will start it.
func (c *Client) Builder() (packer.Builder, error) { func (c *Client) Builder() (packer.Builder, error) {
client, err := c.rpcClient() client, err := c.packrpcClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cmdBuilder{packrpc.Builder(client), c}, nil return &cmdBuilder{client.Builder(), c}, nil
} }
// Returns a command implementation that is communicating over this // Returns a command implementation that is communicating over this
// client. If the client hasn't been started, this will start it. // client. If the client hasn't been started, this will start it.
func (c *Client) Command() (packer.Command, error) { func (c *Client) Command() (packer.Command, error) {
client, err := c.rpcClient() client, err := c.packrpcClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cmdCommand{packrpc.Command(client), c}, nil return &cmdCommand{client.Command(), c}, nil
} }
// Returns a hook implementation that is communicating over this // Returns a hook implementation that is communicating over this
// client. If the client hasn't been started, this will start it. // client. If the client hasn't been started, this will start it.
func (c *Client) Hook() (packer.Hook, error) { func (c *Client) Hook() (packer.Hook, error) {
client, err := c.rpcClient() client, err := c.packrpcClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cmdHook{packrpc.Hook(client), c}, nil return &cmdHook{client.Hook(), c}, nil
} }
// Returns a post-processor implementation that is communicating over // Returns a post-processor implementation that is communicating over
// this client. If the client hasn't been started, this will start it. // this client. If the client hasn't been started, this will start it.
func (c *Client) PostProcessor() (packer.PostProcessor, error) { func (c *Client) PostProcessor() (packer.PostProcessor, error) {
client, err := c.rpcClient() client, err := c.packrpcClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cmdPostProcessor{packrpc.PostProcessor(client), c}, nil return &cmdPostProcessor{client.PostProcessor(), c}, nil
} }
// Returns a provisioner implementation that is communicating over this // Returns a provisioner implementation that is communicating over this
// client. If the client hasn't been started, this will start it. // client. If the client hasn't been started, this will start it.
func (c *Client) Provisioner() (packer.Provisioner, error) { func (c *Client) Provisioner() (packer.Provisioner, error) {
client, err := c.rpcClient() client, err := c.packrpcClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cmdProvisioner{packrpc.Provisioner(client), c}, nil return &cmdProvisioner{client.Provisioner(), c}, nil
} }
// End the executing subprocess (if it is running) and perform any cleanup // End the executing subprocess (if it is running) and perform any cleanup
...@@ -361,7 +360,7 @@ func (c *Client) logStderr(r io.Reader) { ...@@ -361,7 +360,7 @@ func (c *Client) logStderr(r io.Reader) {
close(c.doneLogging) close(c.doneLogging)
} }
func (c *Client) rpcClient() (*rpc.Client, error) { func (c *Client) packrpcClient() (*packrpc.Client, error) {
address, err := c.Start() address, err := c.Start()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -376,5 +375,11 @@ func (c *Client) rpcClient() (*rpc.Client, error) { ...@@ -376,5 +375,11 @@ func (c *Client) rpcClient() (*rpc.Client, error) {
tcpConn := conn.(*net.TCPConn) tcpConn := conn.(*net.TCPConn)
tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlive(true)
return rpc.NewClient(tcpConn), nil client, err := packrpc.NewClient(tcpConn)
if err != nil {
tcpConn.Close()
return nil, err
}
return client, nil
} }
...@@ -14,7 +14,6 @@ import ( ...@@ -14,7 +14,6 @@ import (
packrpc "github.com/mitchellh/packer/packer/rpc" packrpc "github.com/mitchellh/packer/packer/rpc"
"log" "log"
"net" "net"
"net/rpc"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
...@@ -35,13 +34,14 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69 ...@@ -35,13 +34,14 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69
// know how to speak it. // know how to speak it.
const APIVersion = "1" const APIVersion = "1"
// This serves a single RPC connection on the given RPC server on // Server waits for a connection to this plugin and returns a Packer
// a random port. // RPC server that you can use to register components and serve them.
func serve(server *rpc.Server) (err error) { func Server() (*packrpc.Server, error) {
log.Printf("Plugin build against Packer '%s'", packer.GitCommit) log.Printf("Plugin build against Packer '%s'", packer.GitCommit)
if os.Getenv(MagicCookieKey) != MagicCookieValue { if os.Getenv(MagicCookieKey) != MagicCookieValue {
return errors.New("Please do not execute plugins directly. Packer will execute these for you.") return nil, errors.New(
"Please do not execute plugins directly. Packer will execute these for you.")
} }
// If there is no explicit number of Go threads to use, then set it // If there is no explicit number of Go threads to use, then set it
...@@ -51,12 +51,12 @@ func serve(server *rpc.Server) (err error) { ...@@ -51,12 +51,12 @@ func serve(server *rpc.Server) (err error) {
minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32) minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32)
if err != nil { if err != nil {
return return nil, err
} }
maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32) maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32)
if err != nil { if err != nil {
return return nil, err
} }
log.Printf("Plugin minimum port: %d\n", minPort) log.Printf("Plugin minimum port: %d\n", minPort)
...@@ -77,7 +77,6 @@ func serve(server *rpc.Server) (err error) { ...@@ -77,7 +77,6 @@ func serve(server *rpc.Server) (err error) {
break break
} }
defer listener.Close() defer listener.Close()
// Output the address to stdout // Output the address to stdout
...@@ -90,102 +89,22 @@ func serve(server *rpc.Server) (err error) { ...@@ -90,102 +89,22 @@ func serve(server *rpc.Server) (err error) {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
log.Printf("Error accepting connection: %s\n", err.Error()) log.Printf("Error accepting connection: %s\n", err.Error())
return return nil, err
} }
// Serve a single connection // Eat the interrupts
log.Println("Serving a plugin connection...")
server.ServeConn(conn)
return
}
// Registers a signal handler to swallow and count interrupts so that the
// plugin isn't killed. The main host Packer process is responsible
// for killing the plugins when interrupted.
func countInterrupts() {
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt) signal.Notify(ch, os.Interrupt)
go func() { go func() {
var count int32 = 0
for { for {
<-ch <-ch
newCount := atomic.AddInt32(&Interrupts, 1) newCount := atomic.AddInt32(&count, 1)
log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount) log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount)
} }
}() }()
}
// Serves a builder from a plugin.
func ServeBuilder(builder packer.Builder) {
log.Println("Preparing to serve a builder plugin...")
server := rpc.NewServer()
packrpc.RegisterBuilder(server, builder)
countInterrupts()
if err := serve(server); err != nil {
log.Printf("ERROR: %s", err)
os.Exit(1)
}
}
// Serves a command from a plugin.
func ServeCommand(command packer.Command) {
log.Println("Preparing to serve a command plugin...")
server := rpc.NewServer()
packrpc.RegisterCommand(server, command)
countInterrupts()
if err := serve(server); err != nil {
log.Printf("ERROR: %s", err)
os.Exit(1)
}
}
// Serves a hook from a plugin. // Serve a single connection
func ServeHook(hook packer.Hook) { log.Println("Serving a plugin connection...")
log.Println("Preparing to serve a hook plugin...") return packrpc.NewServer(conn), nil
server := rpc.NewServer()
packrpc.RegisterHook(server, hook)
countInterrupts()
if err := serve(server); err != nil {
log.Printf("ERROR: %s", err)
os.Exit(1)
}
}
// Serves a post-processor from a plugin.
func ServePostProcessor(p packer.PostProcessor) {
log.Println("Preparing to serve a post-processor plugin...")
server := rpc.NewServer()
packrpc.RegisterPostProcessor(server, p)
countInterrupts()
if err := serve(server); err != nil {
log.Printf("ERROR: %s", err)
os.Exit(1)
}
}
// Serves a provisioner from a plugin.
func ServeProvisioner(p packer.Provisioner) {
log.Println("Preparing to serve a provisioner plugin...")
server := rpc.NewServer()
packrpc.RegisterProvisioner(server, p)
countInterrupts()
if err := serve(server); err != nil {
log.Printf("ERROR: %s", err)
os.Exit(1)
}
}
// Tests whether or not the plugin was interrupted or not.
func Interrupted() bool {
return atomic.LoadInt32(&Interrupts) > 0
} }
...@@ -54,20 +54,50 @@ func TestHelperProcess(*testing.T) { ...@@ -54,20 +54,50 @@ func TestHelperProcess(*testing.T) {
fmt.Printf("%s1|:1234\n", APIVersion) fmt.Printf("%s1|:1234\n", APIVersion)
<-make(chan int) <-make(chan int)
case "builder": case "builder":
ServeBuilder(new(packer.MockBuilder)) server, err := Server()
if err != nil {
log.Printf("[ERR] %s", err)
os.Exit(1)
}
server.RegisterBuilder(new(packer.MockBuilder))
server.Serve()
case "command": case "command":
ServeCommand(new(helperCommand)) server, err := Server()
if err != nil {
log.Printf("[ERR] %s", err)
os.Exit(1)
}
server.RegisterCommand(new(helperCommand))
server.Serve()
case "hook": case "hook":
ServeHook(new(packer.MockHook)) server, err := Server()
if err != nil {
log.Printf("[ERR] %s", err)
os.Exit(1)
}
server.RegisterHook(new(packer.MockHook))
server.Serve()
case "invalid-rpc-address": case "invalid-rpc-address":
fmt.Println("lolinvalid") fmt.Println("lolinvalid")
case "mock": case "mock":
fmt.Printf("%s|:1234\n", APIVersion) fmt.Printf("%s|:1234\n", APIVersion)
<-make(chan int) <-make(chan int)
case "post-processor": case "post-processor":
ServePostProcessor(new(helperPostProcessor)) server, err := Server()
if err != nil {
log.Printf("[ERR] %s", err)
os.Exit(1)
}
server.RegisterPostProcessor(new(helperPostProcessor))
server.Serve()
case "provisioner": case "provisioner":
ServeProvisioner(new(packer.MockProvisioner)) server, err := Server()
if err != nil {
log.Printf("[ERR] %s", err)
os.Exit(1)
}
server.RegisterProvisioner(new(packer.MockProvisioner))
server.Serve()
case "start-timeout": case "start-timeout":
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
os.Exit(1) os.Exit(1)
......
...@@ -8,7 +8,8 @@ import ( ...@@ -8,7 +8,8 @@ import (
// An implementation of packer.Artifact where the artifact is actually // An implementation of packer.Artifact where the artifact is actually
// available over an RPC connection. // available over an RPC connection.
type artifact struct { type artifact struct {
client *rpc.Client client *rpc.Client
endpoint string
} }
// ArtifactServer wraps a packer.Artifact implementation and makes it // ArtifactServer wraps a packer.Artifact implementation and makes it
...@@ -17,33 +18,29 @@ type ArtifactServer struct { ...@@ -17,33 +18,29 @@ type ArtifactServer struct {
artifact packer.Artifact artifact packer.Artifact
} }
func Artifact(client *rpc.Client) *artifact {
return &artifact{client}
}
func (a *artifact) BuilderId() (result string) { func (a *artifact) BuilderId() (result string) {
a.client.Call("Artifact.BuilderId", new(interface{}), &result) a.client.Call(a.endpoint+".BuilderId", new(interface{}), &result)
return return
} }
func (a *artifact) Files() (result []string) { func (a *artifact) Files() (result []string) {
a.client.Call("Artifact.Files", new(interface{}), &result) a.client.Call(a.endpoint+".Files", new(interface{}), &result)
return return
} }
func (a *artifact) Id() (result string) { func (a *artifact) Id() (result string) {
a.client.Call("Artifact.Id", new(interface{}), &result) a.client.Call(a.endpoint+".Id", new(interface{}), &result)
return return
} }
func (a *artifact) String() (result string) { func (a *artifact) String() (result string) {
a.client.Call("Artifact.String", new(interface{}), &result) a.client.Call(a.endpoint+".String", new(interface{}), &result)
return return
} }
func (a *artifact) Destroy() error { func (a *artifact) Destroy() error {
var result error var result error
if err := a.client.Call("Artifact.Destroy", new(interface{}), &result); err != nil { if err := a.client.Call(a.endpoint+".Destroy", new(interface{}), &result); err != nil {
return err return err
} }
......
...@@ -2,48 +2,21 @@ package rpc ...@@ -2,48 +2,21 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
type testArtifact struct{}
func (testArtifact) BuilderId() string {
return "bid"
}
func (testArtifact) Files() []string {
return []string{"a", "b"}
}
func (testArtifact) Id() string {
return "id"
}
func (testArtifact) String() string {
return "string"
}
func (testArtifact) Destroy() error {
return nil
}
func TestArtifactRPC(t *testing.T) { func TestArtifactRPC(t *testing.T) {
// Create the interface to test // Create the interface to test
a := new(testArtifact) a := new(packer.MockArtifact)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterArtifact(server, a) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterArtifact(a)
// Create the client over RPC and run some methods to verify it works aClient := client.Artifact()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
aClient := Artifact(client)
// Test // Test
if aClient.BuilderId() != "bid" { if aClient.BuilderId() != "bid" {
...@@ -64,5 +37,5 @@ func TestArtifactRPC(t *testing.T) { ...@@ -64,5 +37,5 @@ func TestArtifactRPC(t *testing.T) {
} }
func TestArtifact_Implements(t *testing.T) { func TestArtifact_Implements(t *testing.T) {
var _ packer.Artifact = Artifact(nil) var _ packer.Artifact = new(artifact)
} }
...@@ -9,16 +9,14 @@ import ( ...@@ -9,16 +9,14 @@ import (
// over an RPC connection. // over an RPC connection.
type build struct { type build struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// BuildServer wraps a packer.Build implementation and makes it exportable // BuildServer wraps a packer.Build implementation and makes it exportable
// as part of a Golang RPC server. // as part of a Golang RPC server.
type BuildServer struct { type BuildServer struct {
build packer.Build build packer.Build
} mux *MuxConn
type BuildRunArgs struct {
UiRPCAddress string
} }
type BuildPrepareResponse struct { type BuildPrepareResponse struct {
...@@ -26,10 +24,6 @@ type BuildPrepareResponse struct { ...@@ -26,10 +24,6 @@ type BuildPrepareResponse struct {
Error error Error error
} }
func Build(client *rpc.Client) *build {
return &build{client}
}
func (b *build) Name() (result string) { func (b *build) Name() (result string) {
b.client.Call("Build.Name", new(interface{}), &result) b.client.Call("Build.Name", new(interface{}), &result)
return return
...@@ -45,25 +39,25 @@ func (b *build) Prepare(v map[string]string) ([]string, error) { ...@@ -45,25 +39,25 @@ func (b *build) Prepare(v map[string]string) ([]string, error) {
} }
func (b *build) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error) { func (b *build) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error) {
// Create and start the server for the UI nextId := b.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(b.mux, nextId)
RegisterCache(server, cache) server.RegisterCache(cache)
RegisterUi(server, ui) server.RegisterUi(ui)
args := &BuildRunArgs{serveSingleConn(server)} go server.Serve()
var result []string var result []uint32
if err := b.client.Call("Build.Run", args, &result); err != nil { if err := b.client.Call("Build.Run", nextId, &result); err != nil {
return nil, err return nil, err
} }
artifacts := make([]packer.Artifact, len(result)) artifacts := make([]packer.Artifact, len(result))
for i, addr := range result { for i, streamId := range result {
client, err := rpcDial(addr) client, err := NewClientWithMux(b.mux, streamId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
artifacts[i] = Artifact(client) artifacts[i] = client.Artifact()
} }
return artifacts, nil return artifacts, nil
...@@ -101,22 +95,26 @@ func (b *BuildServer) Prepare(v map[string]string, resp *BuildPrepareResponse) e ...@@ -101,22 +95,26 @@ func (b *BuildServer) Prepare(v map[string]string, resp *BuildPrepareResponse) e
return nil return nil
} }
func (b *BuildServer) Run(args *BuildRunArgs, reply *[]string) error { func (b *BuildServer) Run(streamId uint32, reply *[]uint32) error {
client, err := rpcDial(args.UiRPCAddress) client, err := NewClientWithMux(b.mux, streamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
artifacts, err := b.build.Run(&Ui{client}, Cache(client)) artifacts, err := b.build.Run(client.Ui(), client.Cache())
if err != nil { if err != nil {
return NewBasicError(err) return NewBasicError(err)
} }
*reply = make([]string, len(artifacts)) *reply = make([]uint32, len(artifacts))
for i, artifact := range artifacts { for i, artifact := range artifacts {
server := rpc.NewServer() streamId := b.mux.NextId()
RegisterArtifact(server, artifact) server := NewServerWithMux(b.mux, streamId)
(*reply)[i] = serveSingleConn(server) server.RegisterArtifact(artifact)
go server.Serve()
(*reply)[i] = streamId
} }
return nil return nil
......
...@@ -3,12 +3,11 @@ package rpc ...@@ -3,12 +3,11 @@ package rpc
import ( import (
"errors" "errors"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
var testBuildArtifact = &testArtifact{} var testBuildArtifact = &packer.MockArtifact{}
type testBuild struct { type testBuild struct {
nameCalled bool nameCalled bool
...@@ -60,25 +59,13 @@ func (b *testBuild) Cancel() { ...@@ -60,25 +59,13 @@ func (b *testBuild) Cancel() {
b.cancelCalled = true b.cancelCalled = true
} }
func buildRPCClient(t *testing.T) (*testBuild, packer.Build) {
// Create the interface to test
b := new(testBuild)
// Start the server
server := rpc.NewServer()
RegisterBuild(server, b)
address := serveSingleConn(server)
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
return b, Build(client)
}
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
b, bClient := buildRPCClient(t) b := new(testBuild)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuild(b)
bClient := client.Build()
// Test Name // Test Name
bClient.Name() bClient.Name()
...@@ -120,23 +107,6 @@ func TestBuild(t *testing.T) { ...@@ -120,23 +107,6 @@ func TestBuild(t *testing.T) {
t.Fatalf("bad: %#v", artifacts) t.Fatalf("bad: %#v", artifacts)
} }
// Test the UI given to run, which should be fully functional
if b.runCalled {
b.runCache.Lock("foo")
if !cache.lockCalled {
t.Fatal("lock shuld be called")
}
b.runUi.Say("format")
if !ui.sayCalled {
t.Fatal("say should be called")
}
if ui.sayMessage != "format" {
t.Fatalf("bad: %#v", ui.sayMessage)
}
}
// Test run with an error // Test run with an error
b.errRunResult = true b.errRunResult = true
_, err = bClient.Run(ui, cache) _, err = bClient.Run(ui, cache)
...@@ -164,7 +134,12 @@ func TestBuild(t *testing.T) { ...@@ -164,7 +134,12 @@ func TestBuild(t *testing.T) {
} }
func TestBuildPrepare_Warnings(t *testing.T) { func TestBuildPrepare_Warnings(t *testing.T) {
b, bClient := buildRPCClient(t) b := new(testBuild)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuild(b)
bClient := client.Build()
expected := []string{"foo"} expected := []string{"foo"}
b.prepareWarnings = expected b.prepareWarnings = expected
...@@ -179,5 +154,5 @@ func TestBuildPrepare_Warnings(t *testing.T) { ...@@ -179,5 +154,5 @@ func TestBuildPrepare_Warnings(t *testing.T) {
} }
func TestBuild_ImplementsBuild(t *testing.T) { func TestBuild_ImplementsBuild(t *testing.T) {
var _ packer.Build = Build(nil) var _ packer.Build = new(build)
} }
package rpc package rpc
import ( import (
"encoding/gob"
"fmt"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log" "log"
"net/rpc" "net/rpc"
...@@ -12,37 +10,25 @@ import ( ...@@ -12,37 +10,25 @@ import (
// over an RPC connection. // over an RPC connection.
type builder struct { type builder struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// BuilderServer wraps a packer.Builder implementation and makes it exportable // BuilderServer wraps a packer.Builder implementation and makes it exportable
// as part of a Golang RPC server. // as part of a Golang RPC server.
type BuilderServer struct { type BuilderServer struct {
builder packer.Builder builder packer.Builder
mux *MuxConn
} }
type BuilderPrepareArgs struct { type BuilderPrepareArgs struct {
Configs []interface{} Configs []interface{}
} }
type BuilderRunArgs struct {
RPCAddress string
ResponseAddress string
}
type BuilderPrepareResponse struct { type BuilderPrepareResponse struct {
Warnings []string Warnings []string
Error error Error error
} }
type BuilderRunResponse struct {
Err error
RPCAddress string
}
func Builder(client *rpc.Client) *builder {
return &builder{client}
}
func (b *builder) Prepare(config ...interface{}) ([]string, error) { func (b *builder) Prepare(config ...interface{}) ([]string, error) {
var resp BuilderPrepareResponse var resp BuilderPrepareResponse
cerr := b.client.Call("Builder.Prepare", &BuilderPrepareArgs{config}, &resp) cerr := b.client.Call("Builder.Prepare", &BuilderPrepareArgs{config}, &resp)
...@@ -54,58 +40,28 @@ func (b *builder) Prepare(config ...interface{}) ([]string, error) { ...@@ -54,58 +40,28 @@ func (b *builder) Prepare(config ...interface{}) ([]string, error) {
} }
func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
// Create and start the server for the Build and UI nextId := b.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(b.mux, nextId)
RegisterCache(server, cache) server.RegisterCache(cache)
RegisterHook(server, hook) server.RegisterHook(hook)
RegisterUi(server, ui) server.RegisterUi(ui)
go server.Serve()
// Create a server for the response
responseL := netListenerInRange(portRangeMin, portRangeMax) var responseId uint32
runResponseCh := make(chan *BuilderRunResponse) if err := b.client.Call("Builder.Run", nextId, &responseId); err != nil {
go func() {
defer responseL.Close()
var response BuilderRunResponse
defer func() { runResponseCh <- &response }()
conn, err := responseL.Accept()
if err != nil {
response.Err = err
return
}
defer conn.Close()
decoder := gob.NewDecoder(conn)
if err := decoder.Decode(&response); err != nil {
response.Err = fmt.Errorf("Error waiting for Run: %s", err)
}
}()
args := &BuilderRunArgs{
serveSingleConn(server),
responseL.Addr().String(),
}
if err := b.client.Call("Builder.Run", args, new(interface{})); err != nil {
return nil, err return nil, err
} }
response := <-runResponseCh if responseId == 0 {
if response.Err != nil {
return nil, response.Err
}
if response.RPCAddress == "" {
return nil, nil return nil, nil
} }
client, err := rpcDial(response.RPCAddress) client, err := NewClientWithMux(b.mux, responseId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return Artifact(client), nil return client.Artifact(), nil
} }
func (b *builder) Cancel() { func (b *builder) Cancel() {
...@@ -127,45 +83,26 @@ func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *BuilderPrepareR ...@@ -127,45 +83,26 @@ func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *BuilderPrepareR
return nil return nil
} }
func (b *BuilderServer) Run(args *BuilderRunArgs, reply *interface{}) error { func (b *BuilderServer) Run(streamId uint32, reply *uint32) error {
client, err := rpcDial(args.RPCAddress) client, err := NewClientWithMux(b.mux, streamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
responseC, err := tcpDial(args.ResponseAddress) artifact, err := b.builder.Run(client.Ui(), client.Hook(), client.Cache())
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
responseWriter := gob.NewEncoder(responseC) *reply = 0
if artifact != nil {
// Run the build in a goroutine so we don't block the RPC connection streamId = b.mux.NextId()
go func() { server := NewServerWithMux(b.mux, streamId)
defer responseC.Close() server.RegisterArtifact(artifact)
go server.Serve()
cache := Cache(client) *reply = streamId
hook := Hook(client) }
ui := &Ui{client}
artifact, responseErr := b.builder.Run(ui, hook, cache)
responseAddress := ""
if responseErr == nil && artifact != nil {
// Wrap the artifact
server := rpc.NewServer()
RegisterArtifact(server, artifact)
responseAddress = serveSingleConn(server)
}
if responseErr != nil {
responseErr = NewBasicError(responseErr)
}
err := responseWriter.Encode(&BuilderRunResponse{responseErr, responseAddress})
if err != nil {
log.Printf("BuildServer.Run error: %s", err)
}
}()
return nil return nil
} }
......
...@@ -2,31 +2,19 @@ package rpc ...@@ -2,31 +2,19 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
var testBuilderArtifact = &testArtifact{} var testBuilderArtifact = &packer.MockArtifact{}
func builderRPCClient(t *testing.T) (*packer.MockBuilder, packer.Builder) {
b := new(packer.MockBuilder)
// Start the server
server := rpc.NewServer()
RegisterBuilder(server, b)
address := serveSingleConn(server)
// Create the client over RPC and run some methods to verify it works
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
return b, Builder(client)
}
func TestBuilderPrepare(t *testing.T) { func TestBuilderPrepare(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
// Test Prepare // Test Prepare
config := 42 config := 42
...@@ -48,7 +36,12 @@ func TestBuilderPrepare(t *testing.T) { ...@@ -48,7 +36,12 @@ func TestBuilderPrepare(t *testing.T) {
} }
func TestBuilderPrepare_Warnings(t *testing.T) { func TestBuilderPrepare_Warnings(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
expected := []string{"foo"} expected := []string{"foo"}
b.PrepareWarnings = expected b.PrepareWarnings = expected
...@@ -64,7 +57,12 @@ func TestBuilderPrepare_Warnings(t *testing.T) { ...@@ -64,7 +57,12 @@ func TestBuilderPrepare_Warnings(t *testing.T) {
} }
func TestBuilderRun(t *testing.T) { func TestBuilderRun(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
// Test Run // Test Run
cache := new(testCache) cache := new(testCache)
...@@ -79,34 +77,21 @@ func TestBuilderRun(t *testing.T) { ...@@ -79,34 +77,21 @@ func TestBuilderRun(t *testing.T) {
t.Fatal("run should be called") t.Fatal("run should be called")
} }
b.RunCache.Lock("foo")
if !cache.lockCalled {
t.Fatal("should be called")
}
b.RunHook.Run("foo", nil, nil, nil)
if !hook.RunCalled {
t.Fatal("should be called")
}
b.RunUi.Say("format")
if !ui.sayCalled {
t.Fatal("say should be called")
}
if ui.sayMessage != "format" {
t.Fatalf("bad: %s", ui.sayMessage)
}
if artifact.Id() != testBuilderArtifact.Id() { if artifact.Id() != testBuilderArtifact.Id() {
t.Fatalf("bad: %s", artifact.Id()) t.Fatalf("bad: %s", artifact.Id())
} }
} }
func TestBuilderRun_nilResult(t *testing.T) { func TestBuilderRun_nilResult(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
b.RunNilResult = true b.RunNilResult = true
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
cache := new(testCache) cache := new(testCache)
hook := &packer.MockHook{} hook := &packer.MockHook{}
ui := &testUi{} ui := &testUi{}
...@@ -120,7 +105,13 @@ func TestBuilderRun_nilResult(t *testing.T) { ...@@ -120,7 +105,13 @@ func TestBuilderRun_nilResult(t *testing.T) {
} }
func TestBuilderRun_ErrResult(t *testing.T) { func TestBuilderRun_ErrResult(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
b.RunErrResult = true b.RunErrResult = true
cache := new(testCache) cache := new(testCache)
...@@ -136,7 +127,12 @@ func TestBuilderRun_ErrResult(t *testing.T) { ...@@ -136,7 +127,12 @@ func TestBuilderRun_ErrResult(t *testing.T) {
} }
func TestBuilderCancel(t *testing.T) { func TestBuilderCancel(t *testing.T) {
b, bClient := builderRPCClient(t) b := new(packer.MockBuilder)
client, server := testClientServer(t)
defer client.Close()
defer server.Close()
server.RegisterBuilder(b)
bClient := client.Builder()
bClient.Cancel() bClient.Cancel()
if !b.CancelCalled { if !b.CancelCalled {
...@@ -145,5 +141,5 @@ func TestBuilderCancel(t *testing.T) { ...@@ -145,5 +141,5 @@ func TestBuilderCancel(t *testing.T) {
} }
func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilder_ImplementsBuilder(t *testing.T) {
var _ packer.Builder = Builder(nil) var _ packer.Builder = new(builder)
} }
...@@ -17,10 +17,6 @@ type CacheServer struct { ...@@ -17,10 +17,6 @@ type CacheServer struct {
cache packer.Cache cache packer.Cache
} }
func Cache(client *rpc.Client) *cache {
return &cache{client}
}
type CacheRLockResponse struct { type CacheRLockResponse struct {
Path string Path string
Exists bool Exists bool
......
...@@ -2,7 +2,6 @@ package rpc ...@@ -2,7 +2,6 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"testing" "testing"
) )
...@@ -40,11 +39,7 @@ func (t *testCache) RUnlock(key string) { ...@@ -40,11 +39,7 @@ func (t *testCache) RUnlock(key string) {
} }
func TestCache_Implements(t *testing.T) { func TestCache_Implements(t *testing.T) {
var raw interface{} var _ packer.Cache = new(cache)
raw = Cache(nil)
if _, ok := raw.(packer.Cache); !ok {
t.Fatal("Cache must be a cache.")
}
} }
func TestCacheRPC(t *testing.T) { func TestCacheRPC(t *testing.T) {
...@@ -52,19 +47,15 @@ func TestCacheRPC(t *testing.T) { ...@@ -52,19 +47,15 @@ func TestCacheRPC(t *testing.T) {
c := new(testCache) c := new(testCache)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterCache(server, c) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterCache(c)
// Create the client over RPC and run some methods to verify it works cacheClient := client.Cache()
rpcClient, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("bad: %s", err)
}
client := Cache(rpcClient)
// Test Lock // Test Lock
client.Lock("foo") cacheClient.Lock("foo")
if !c.lockCalled { if !c.lockCalled {
t.Fatal("should be called") t.Fatal("should be called")
} }
...@@ -73,7 +64,7 @@ func TestCacheRPC(t *testing.T) { ...@@ -73,7 +64,7 @@ func TestCacheRPC(t *testing.T) {
} }
// Test Unlock // Test Unlock
client.Unlock("foo") cacheClient.Unlock("foo")
if !c.unlockCalled { if !c.unlockCalled {
t.Fatal("should be called") t.Fatal("should be called")
} }
...@@ -82,7 +73,7 @@ func TestCacheRPC(t *testing.T) { ...@@ -82,7 +73,7 @@ func TestCacheRPC(t *testing.T) {
} }
// Test RLock // Test RLock
client.RLock("foo") cacheClient.RLock("foo")
if !c.rlockCalled { if !c.rlockCalled {
t.Fatal("should be called") t.Fatal("should be called")
} }
...@@ -91,7 +82,7 @@ func TestCacheRPC(t *testing.T) { ...@@ -91,7 +82,7 @@ func TestCacheRPC(t *testing.T) {
} }
// Test RUnlock // Test RUnlock
client.RUnlock("foo") cacheClient.RUnlock("foo")
if !c.runlockCalled { if !c.runlockCalled {
t.Fatal("should be called") t.Fatal("should be called")
} }
......
package rpc
import (
"github.com/mitchellh/packer/packer"
"io"
"net/rpc"
)
// Client is the client end that communicates with a Packer RPC server.
// Establishing a connection is up to the user, the Client can just
// communicate over any ReadWriteCloser.
type Client struct {
mux *MuxConn
client *rpc.Client
}
func NewClient(rwc io.ReadWriteCloser) (*Client, error) {
return NewClientWithMux(NewMuxConn(rwc), 0)
}
func NewClientWithMux(mux *MuxConn, streamId uint32) (*Client, error) {
clientConn, err := mux.Dial(streamId)
if err != nil {
return nil, err
}
return &Client{
mux: mux,
client: rpc.NewClient(clientConn),
}, nil
}
func (c *Client) Close() error {
if err := c.client.Close(); err != nil {
return err
}
return nil
}
func (c *Client) Artifact() packer.Artifact {
return &artifact{
client: c.client,
endpoint: DefaultArtifactEndpoint,
}
}
func (c *Client) Build() packer.Build {
return &build{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Builder() packer.Builder {
return &builder{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Cache() packer.Cache {
return &cache{
client: c.client,
}
}
func (c *Client) Command() packer.Command {
return &command{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Communicator() packer.Communicator {
return &communicator{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Environment() packer.Environment {
return &Environment{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Hook() packer.Hook {
return &hook{
client: c.client,
mux: c.mux,
}
}
func (c *Client) PostProcessor() packer.PostProcessor {
return &postProcessor{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Provisioner() packer.Provisioner {
return &provisioner{
client: c.client,
mux: c.mux,
}
}
func (c *Client) Ui() packer.Ui {
return &Ui{
client: c.client,
endpoint: DefaultUiEndpoint,
}
}
package rpc
import (
"net"
"testing"
)
func testConn(t *testing.T) (net.Conn, net.Conn) {
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("err: %s", err)
}
var serverConn net.Conn
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
defer l.Close()
var err error
serverConn, err = l.Accept()
if err != nil {
t.Fatalf("err: %s", err)
}
}()
clientConn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Fatalf("err: %s", err)
}
<-doneCh
return clientConn, serverConn
}
func testClientServer(t *testing.T) (*Client, *Server) {
clientConn, serverConn := testConn(t)
server := NewServer(serverConn)
go server.Serve()
client, err := NewClient(clientConn)
if err != nil {
server.Close()
t.Fatalf("err: %s", err)
}
return client, server
}
...@@ -9,25 +9,23 @@ import ( ...@@ -9,25 +9,23 @@ import (
// command is actually executed over an RPC connection. // command is actually executed over an RPC connection.
type command struct { type command struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// A CommandServer wraps a packer.Command and makes it exportable as part // A CommandServer wraps a packer.Command and makes it exportable as part
// of a Golang RPC server. // of a Golang RPC server.
type CommandServer struct { type CommandServer struct {
command packer.Command command packer.Command
mux *MuxConn
} }
type CommandRunArgs struct { type CommandRunArgs struct {
RPCAddress string
Args []string Args []string
StreamId uint32
} }
type CommandSynopsisArgs byte type CommandSynopsisArgs byte
func Command(client *rpc.Client) *command {
return &command{client}
}
func (c *command) Help() (result string) { func (c *command) Help() (result string) {
err := c.client.Call("Command.Help", new(interface{}), &result) err := c.client.Call("Command.Help", new(interface{}), &result)
if err != nil { if err != nil {
...@@ -38,11 +36,15 @@ func (c *command) Help() (result string) { ...@@ -38,11 +36,15 @@ func (c *command) Help() (result string) {
} }
func (c *command) Run(env packer.Environment, args []string) (result int) { func (c *command) Run(env packer.Environment, args []string) (result int) {
// Create and start the server for the Environment nextId := c.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(c.mux, nextId)
RegisterEnvironment(server, env) server.RegisterEnvironment(env)
go server.Serve()
rpcArgs := &CommandRunArgs{serveSingleConn(server), args}
rpcArgs := &CommandRunArgs{
Args: args,
StreamId: nextId,
}
err := c.client.Call("Command.Run", rpcArgs, &result) err := c.client.Call("Command.Run", rpcArgs, &result)
if err != nil { if err != nil {
panic(err) panic(err)
...@@ -66,14 +68,13 @@ func (c *CommandServer) Help(args *interface{}, reply *string) error { ...@@ -66,14 +68,13 @@ func (c *CommandServer) Help(args *interface{}, reply *string) error {
} }
func (c *CommandServer) Run(args *CommandRunArgs, reply *int) error { func (c *CommandServer) Run(args *CommandRunArgs, reply *int) error {
client, err := rpcDial(args.RPCAddress) client, err := NewClientWithMux(c.mux, args.StreamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
env := &Environment{client} *reply = c.command.Run(client.Environment(), args.Args)
*reply = c.command.Run(env, args.Args)
return nil return nil
} }
......
...@@ -2,7 +2,6 @@ package rpc ...@@ -2,7 +2,6 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
...@@ -33,21 +32,14 @@ func TestRPCCommand(t *testing.T) { ...@@ -33,21 +32,14 @@ func TestRPCCommand(t *testing.T) {
command := new(TestCommand) command := new(TestCommand)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterCommand(server, command) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterCommand(command)
// Create the command client over RPC and run some methods to verify commClient := client.Command()
// we get the proper behavior.
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
clientComm := Command(client)
//Test Help //Test Help
help := clientComm.Help() help := commClient.Help()
if help != "bar" { if help != "bar" {
t.Fatalf("bad: %s", help) t.Fatalf("bad: %s", help)
} }
...@@ -55,7 +47,7 @@ func TestRPCCommand(t *testing.T) { ...@@ -55,7 +47,7 @@ func TestRPCCommand(t *testing.T) {
// Test run // Test run
runArgs := []string{"foo", "bar"} runArgs := []string{"foo", "bar"}
testEnv := &testEnvironment{} testEnv := &testEnvironment{}
exitCode := clientComm.Run(testEnv, runArgs) exitCode := commClient.Run(testEnv, runArgs)
if !reflect.DeepEqual(command.runArgs, runArgs) { if !reflect.DeepEqual(command.runArgs, runArgs) {
t.Fatalf("bad: %#v", command.runArgs) t.Fatalf("bad: %#v", command.runArgs)
} }
...@@ -67,18 +59,13 @@ func TestRPCCommand(t *testing.T) { ...@@ -67,18 +59,13 @@ func TestRPCCommand(t *testing.T) {
t.Fatal("runEnv should not be nil") t.Fatal("runEnv should not be nil")
} }
command.runEnv.Ui()
if !testEnv.uiCalled {
t.Fatal("ui should be called")
}
// Test Synopsis // Test Synopsis
synopsis := clientComm.Synopsis() synopsis := commClient.Synopsis()
if synopsis != "foo" { if synopsis != "foo" {
t.Fatalf("bad: %#v", synopsis) t.Fatalf("bad: %#v", synopsis)
} }
} }
func TestCommand_Implements(t *testing.T) { func TestCommand_Implements(t *testing.T) {
var _ packer.Command = Command(nil) var _ packer.Command = new(command)
} }
...@@ -2,11 +2,9 @@ package rpc ...@@ -2,11 +2,9 @@ package rpc
import ( import (
"encoding/gob" "encoding/gob"
"errors"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io" "io"
"log" "log"
"net"
"net/rpc" "net/rpc"
) )
...@@ -14,12 +12,14 @@ import ( ...@@ -14,12 +12,14 @@ import (
// executed over an RPC connection. // executed over an RPC connection.
type communicator struct { type communicator struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// CommunicatorServer wraps a packer.Communicator implementation and makes // CommunicatorServer wraps a packer.Communicator implementation and makes
// it exportable as part of a Golang RPC server. // it exportable as part of a Golang RPC server.
type CommunicatorServer struct { type CommunicatorServer struct {
c packer.Communicator c packer.Communicator
mux *MuxConn
} }
type CommandFinished struct { type CommandFinished struct {
...@@ -27,21 +27,21 @@ type CommandFinished struct { ...@@ -27,21 +27,21 @@ type CommandFinished struct {
} }
type CommunicatorStartArgs struct { type CommunicatorStartArgs struct {
Command string Command string
StdinAddress string StdinStreamId uint32
StdoutAddress string StdoutStreamId uint32
StderrAddress string StderrStreamId uint32
ResponseAddress string ResponseStreamId uint32
} }
type CommunicatorDownloadArgs struct { type CommunicatorDownloadArgs struct {
Path string Path string
WriterAddress string WriterStreamId uint32
} }
type CommunicatorUploadArgs struct { type CommunicatorUploadArgs struct {
Path string Path string
ReaderAddress string ReaderStreamId uint32
} }
type CommunicatorUploadDirArgs struct { type CommunicatorUploadDirArgs struct {
...@@ -51,7 +51,7 @@ type CommunicatorUploadDirArgs struct { ...@@ -51,7 +51,7 @@ type CommunicatorUploadDirArgs struct {
} }
func Communicator(client *rpc.Client) *communicator { func Communicator(client *rpc.Client) *communicator {
return &communicator{client} return &communicator{client: client}
} }
func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) { func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
...@@ -59,45 +59,43 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) { ...@@ -59,45 +59,43 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
args.Command = cmd.Command args.Command = cmd.Command
if cmd.Stdin != nil { if cmd.Stdin != nil {
stdinL := netListenerInRange(portRangeMin, portRangeMax) args.StdinStreamId = c.mux.NextId()
args.StdinAddress = stdinL.Addr().String() go serveSingleCopy("stdin", c.mux, args.StdinStreamId, nil, cmd.Stdin)
go serveSingleCopy("stdin", stdinL, nil, cmd.Stdin)
} }
if cmd.Stdout != nil { if cmd.Stdout != nil {
stdoutL := netListenerInRange(portRangeMin, portRangeMax) args.StdoutStreamId = c.mux.NextId()
args.StdoutAddress = stdoutL.Addr().String() go serveSingleCopy("stdout", c.mux, args.StdoutStreamId, cmd.Stdout, nil)
go serveSingleCopy("stdout", stdoutL, cmd.Stdout, nil)
} }
if cmd.Stderr != nil { if cmd.Stderr != nil {
stderrL := netListenerInRange(portRangeMin, portRangeMax) args.StderrStreamId = c.mux.NextId()
args.StderrAddress = stderrL.Addr().String() go serveSingleCopy("stderr", c.mux, args.StderrStreamId, cmd.Stderr, nil)
go serveSingleCopy("stderr", stderrL, cmd.Stderr, nil)
} }
responseL := netListenerInRange(portRangeMin, portRangeMax) responseStreamId := c.mux.NextId()
args.ResponseAddress = responseL.Addr().String() args.ResponseStreamId = responseStreamId
go func() { go func() {
defer responseL.Close() conn, err := c.mux.Accept(responseStreamId)
conn, err := responseL.Accept()
if err != nil { if err != nil {
log.Printf("[ERR] Error accepting response stream %d: %s",
responseStreamId, err)
cmd.SetExited(123) cmd.SetExited(123)
return return
} }
defer conn.Close() defer conn.Close()
decoder := gob.NewDecoder(conn)
var finished CommandFinished var finished CommandFinished
decoder := gob.NewDecoder(conn)
if err := decoder.Decode(&finished); err != nil { if err := decoder.Decode(&finished); err != nil {
log.Printf("[ERR] Error decoding response stream %d: %s",
responseStreamId, err)
cmd.SetExited(123) cmd.SetExited(123)
return return
} }
log.Printf("[INFO] RPC client: Communicator ended with: %d", finished.ExitStatus)
cmd.SetExited(finished.ExitStatus) cmd.SetExited(finished.ExitStatus)
}() }()
...@@ -106,23 +104,13 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) { ...@@ -106,23 +104,13 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
} }
func (c *communicator) Upload(path string, r io.Reader) (err error) { func (c *communicator) Upload(path string, r io.Reader) (err error) {
// We need to create a server that can proxy the reader data
// over because we can't simply gob encode an io.Reader
readerL := netListenerInRange(portRangeMin, portRangeMax)
if readerL == nil {
err = errors.New("couldn't allocate listener for upload reader")
return
}
// Make sure at the end of this call, we close the listener
defer readerL.Close()
// Pipe the reader through to the connection // Pipe the reader through to the connection
go serveSingleCopy("uploadReader", readerL, nil, r) streamId := c.mux.NextId()
go serveSingleCopy("uploadData", c.mux, streamId, nil, r)
args := CommunicatorUploadArgs{ args := CommunicatorUploadArgs{
path, Path: path,
readerL.Addr().String(), ReaderStreamId: streamId,
} }
err = c.client.Call("Communicator.Upload", &args, new(interface{})) err = c.client.Call("Communicator.Upload", &args, new(interface{}))
...@@ -146,99 +134,104 @@ func (c *communicator) UploadDir(dst string, src string, exclude []string) error ...@@ -146,99 +134,104 @@ func (c *communicator) UploadDir(dst string, src string, exclude []string) error
} }
func (c *communicator) Download(path string, w io.Writer) (err error) { func (c *communicator) Download(path string, w io.Writer) (err error) {
// We need to create a server that can proxy that data downloaded
// into the writer because we can't gob encode a writer directly.
writerL := netListenerInRange(portRangeMin, portRangeMax)
if writerL == nil {
err = errors.New("couldn't allocate listener for download writer")
return
}
// Make sure we close the listener once we're done because we'll be done
defer writerL.Close()
// Serve a single connection and a single copy // Serve a single connection and a single copy
go serveSingleCopy("downloadWriter", writerL, w, nil) streamId := c.mux.NextId()
go serveSingleCopy("downloadWriter", c.mux, streamId, w, nil)
args := CommunicatorDownloadArgs{ args := CommunicatorDownloadArgs{
path, Path: path,
writerL.Addr().String(), WriterStreamId: streamId,
} }
err = c.client.Call("Communicator.Download", &args, new(interface{})) err = c.client.Call("Communicator.Download", &args, new(interface{}))
return return
} }
func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) (err error) { func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) (error) {
// Build the RemoteCmd on this side so that it all pipes over // Build the RemoteCmd on this side so that it all pipes over
// to the remote side. // to the remote side.
var cmd packer.RemoteCmd var cmd packer.RemoteCmd
cmd.Command = args.Command cmd.Command = args.Command
toClose := make([]net.Conn, 0) // Create a channel to signal we're done so that we can close
if args.StdinAddress != "" { // our stdin/stdout/stderr streams
stdinC, err := tcpDial(args.StdinAddress) toClose := make([]io.Closer, 0)
doneCh := make(chan struct{})
go func() {
<-doneCh
for _, conn := range toClose {
defer conn.Close()
}
}()
if args.StdinStreamId > 0 {
conn, err := c.mux.Dial(args.StdinStreamId)
if err != nil { if err != nil {
return err close(doneCh)
return NewBasicError(err)
} }
toClose = append(toClose, stdinC) toClose = append(toClose, conn)
cmd.Stdin = stdinC cmd.Stdin = conn
} }
if args.StdoutAddress != "" { if args.StdoutStreamId > 0 {
stdoutC, err := tcpDial(args.StdoutAddress) conn, err := c.mux.Dial(args.StdoutStreamId)
if err != nil { if err != nil {
return err close(doneCh)
return NewBasicError(err)
} }
toClose = append(toClose, stdoutC) toClose = append(toClose, conn)
cmd.Stdout = stdoutC cmd.Stdout = conn
} }
if args.StderrAddress != "" { if args.StderrStreamId > 0 {
stderrC, err := tcpDial(args.StderrAddress) conn, err := c.mux.Dial(args.StderrStreamId)
if err != nil { if err != nil {
return err close(doneCh)
return NewBasicError(err)
} }
toClose = append(toClose, stderrC) toClose = append(toClose, conn)
cmd.Stderr = stderrC cmd.Stderr = conn
} }
// Connect to the response address so we can write our result to it // Connect to the response address so we can write our result to it
// when ready. // when ready.
responseC, err := tcpDial(args.ResponseAddress) responseC, err := c.mux.Dial(args.ResponseStreamId)
if err != nil { if err != nil {
return err close(doneCh)
return NewBasicError(err)
} }
responseWriter := gob.NewEncoder(responseC) responseWriter := gob.NewEncoder(responseC)
// Start the actual command // Start the actual command
err = c.c.Start(&cmd) err = c.c.Start(&cmd)
if err != nil {
close(doneCh)
return NewBasicError(err)
}
// Start a goroutine to spin and wait for the process to actual // Start a goroutine to spin and wait for the process to actual
// exit. When it does, report it back to caller... // exit. When it does, report it back to caller...
go func() { go func() {
defer close(doneCh)
defer responseC.Close() defer responseC.Close()
for _, conn := range toClose {
defer conn.Close()
}
cmd.Wait() cmd.Wait()
log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus)
responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) responseWriter.Encode(&CommandFinished{cmd.ExitStatus})
}() }()
return return nil
} }
func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) { func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) {
readerC, err := tcpDial(args.ReaderAddress) readerC, err := c.mux.Dial(args.ReaderStreamId)
if err != nil { if err != nil {
return return
} }
defer readerC.Close() defer readerC.Close()
err = c.c.Upload(args.Path, readerC) err = c.c.Upload(args.Path, readerC)
...@@ -250,21 +243,18 @@ func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *e ...@@ -250,21 +243,18 @@ func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *e
} }
func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) { func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
writerC, err := tcpDial(args.WriterAddress) writerC, err := c.mux.Dial(args.WriterStreamId)
if err != nil { if err != nil {
return return
} }
defer writerC.Close() defer writerC.Close()
err = c.c.Download(args.Path, writerC) err = c.c.Download(args.Path, writerC)
return return
} }
func serveSingleCopy(name string, l net.Listener, dst io.Writer, src io.Reader) { func serveSingleCopy(name string, mux *MuxConn, id uint32, dst io.Writer, src io.Reader) {
defer l.Close() conn, err := mux.Accept(id)
conn, err := l.Accept()
if err != nil { if err != nil {
log.Printf("'%s' accept error: %s", name, err) log.Printf("'%s' accept error: %s", name, err)
return return
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io" "io"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
...@@ -14,16 +13,11 @@ func TestCommunicatorRPC(t *testing.T) { ...@@ -14,16 +13,11 @@ func TestCommunicatorRPC(t *testing.T) {
c := new(packer.MockCommunicator) c := new(packer.MockCommunicator)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterCommunicator(server, c) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterCommunicator(c)
// Create the client over RPC and run some methods to verify it works remote := client.Communicator()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
remote := Communicator(client)
// The remote command we'll use // The remote command we'll use
stdin_r, stdin_w := io.Pipe() stdin_r, stdin_w := io.Pipe()
...@@ -42,7 +36,7 @@ func TestCommunicatorRPC(t *testing.T) { ...@@ -42,7 +36,7 @@ func TestCommunicatorRPC(t *testing.T) {
c.StartExitStatus = 42 c.StartExitStatus = 42
// Test Start // Test Start
err = remote.Start(&cmd) err := remote.Start(&cmd)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -74,7 +68,7 @@ func TestCommunicatorRPC(t *testing.T) { ...@@ -74,7 +68,7 @@ func TestCommunicatorRPC(t *testing.T) {
stdin_w.Close() stdin_w.Close()
cmd.Wait() cmd.Wait()
if c.StartStdin != "info\n" { if c.StartStdin != "info\n" {
t.Fatalf("bad data: %s", data) t.Fatalf("bad data: %s", c.StartStdin)
} }
// Test that we can get the exit status properly // Test that we can get the exit status properly
......
...@@ -2,6 +2,7 @@ package rpc ...@@ -2,6 +2,7 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
"net/rpc" "net/rpc"
) )
...@@ -9,12 +10,14 @@ import ( ...@@ -9,12 +10,14 @@ import (
// where the actual environment is executed over an RPC connection. // where the actual environment is executed over an RPC connection.
type Environment struct { type Environment struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// A EnvironmentServer wraps a packer.Environment and makes it exportable // A EnvironmentServer wraps a packer.Environment and makes it exportable
// as part of a Golang RPC server. // as part of a Golang RPC server.
type EnvironmentServer struct { type EnvironmentServer struct {
env packer.Environment env packer.Environment
mux *MuxConn
} }
type EnvironmentCliArgs struct { type EnvironmentCliArgs struct {
...@@ -22,33 +25,32 @@ type EnvironmentCliArgs struct { ...@@ -22,33 +25,32 @@ type EnvironmentCliArgs struct {
} }
func (e *Environment) Builder(name string) (b packer.Builder, err error) { func (e *Environment) Builder(name string) (b packer.Builder, err error) {
var reply string var streamId uint32
err = e.client.Call("Environment.Builder", name, &reply) err = e.client.Call("Environment.Builder", name, &streamId)
if err != nil { if err != nil {
return return
} }
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
return return nil, err
} }
b = client.Builder()
b = Builder(client)
return return
} }
func (e *Environment) Cache() packer.Cache { func (e *Environment) Cache() packer.Cache {
var reply string var streamId uint32
if err := e.client.Call("Environment.Cache", new(interface{}), &reply); err != nil { if err := e.client.Call("Environment.Cache", new(interface{}), &streamId); err != nil {
panic(err) panic(err)
} }
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
panic(err) log.Printf("[ERR] Error getting cache client: %s", err)
return nil
} }
return client.Cache()
return Cache(client)
} }
func (e *Environment) Cli(args []string) (result int, err error) { func (e *Environment) Cli(args []string) (result int, err error) {
...@@ -58,85 +60,81 @@ func (e *Environment) Cli(args []string) (result int, err error) { ...@@ -58,85 +60,81 @@ func (e *Environment) Cli(args []string) (result int, err error) {
} }
func (e *Environment) Hook(name string) (h packer.Hook, err error) { func (e *Environment) Hook(name string) (h packer.Hook, err error) {
var reply string var streamId uint32
err = e.client.Call("Environment.Hook", name, &reply) err = e.client.Call("Environment.Hook", name, &streamId)
if err != nil { if err != nil {
return return
} }
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
return return nil, err
} }
return client.Hook(), nil
h = Hook(client)
return
} }
func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err error) { func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err error) {
var reply string var streamId uint32
err = e.client.Call("Environment.PostProcessor", name, &reply) err = e.client.Call("Environment.PostProcessor", name, &streamId)
if err != nil { if err != nil {
return return
} }
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
return return nil, err
} }
p = client.PostProcessor()
p = PostProcessor(client)
return return
} }
func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) { func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) {
var reply string var streamId uint32
err = e.client.Call("Environment.Provisioner", name, &reply) err = e.client.Call("Environment.Provisioner", name, &streamId)
if err != nil { if err != nil {
return return
} }
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
return return nil, err
} }
p = client.Provisioner()
p = Provisioner(client)
return return
} }
func (e *Environment) Ui() packer.Ui { func (e *Environment) Ui() packer.Ui {
var reply string var streamId uint32
e.client.Call("Environment.Ui", new(interface{}), &reply) e.client.Call("Environment.Ui", new(interface{}), &streamId)
client, err := rpcDial(reply) client, err := NewClientWithMux(e.mux, streamId)
if err != nil { if err != nil {
panic(err) log.Printf("[ERR] Error connecting to Ui: %s", err)
return nil
} }
return client.Ui()
return &Ui{client}
} }
func (e *EnvironmentServer) Builder(name *string, reply *string) error { func (e *EnvironmentServer) Builder(name string, reply *uint32) error {
builder, err := e.env.Builder(*name) builder, err := e.env.Builder(name)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
// Wrap it *reply = e.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(e.mux, *reply)
RegisterBuilder(server, builder) server.RegisterBuilder(builder)
go server.Serve()
*reply = serveSingleConn(server)
return nil return nil
} }
func (e *EnvironmentServer) Cache(args *interface{}, reply *string) error { func (e *EnvironmentServer) Cache(args *interface{}, reply *uint32) error {
cache := e.env.Cache() cache := e.env.Cache()
server := rpc.NewServer() *reply = e.mux.NextId()
RegisterCache(server, cache) server := NewServerWithMux(e.mux, *reply)
*reply = serveSingleConn(server) server.RegisterCache(cache)
go server.Serve()
return nil return nil
} }
...@@ -145,53 +143,51 @@ func (e *EnvironmentServer) Cli(args *EnvironmentCliArgs, reply *int) (err error ...@@ -145,53 +143,51 @@ func (e *EnvironmentServer) Cli(args *EnvironmentCliArgs, reply *int) (err error
return return
} }
func (e *EnvironmentServer) Hook(name *string, reply *string) error { func (e *EnvironmentServer) Hook(name string, reply *uint32) error {
hook, err := e.env.Hook(*name) hook, err := e.env.Hook(name)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
// Wrap it *reply = e.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(e.mux, *reply)
RegisterHook(server, hook) server.RegisterHook(hook)
go server.Serve()
*reply = serveSingleConn(server)
return nil return nil
} }
func (e *EnvironmentServer) PostProcessor(name *string, reply *string) error { func (e *EnvironmentServer) PostProcessor(name string, reply *uint32) error {
pp, err := e.env.PostProcessor(*name) pp, err := e.env.PostProcessor(name)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
server := rpc.NewServer() *reply = e.mux.NextId()
RegisterPostProcessor(server, pp) server := NewServerWithMux(e.mux, *reply)
server.RegisterPostProcessor(pp)
*reply = serveSingleConn(server) go server.Serve()
return nil return nil
} }
func (e *EnvironmentServer) Provisioner(name *string, reply *string) error { func (e *EnvironmentServer) Provisioner(name string, reply *uint32) error {
prov, err := e.env.Provisioner(*name) prov, err := e.env.Provisioner(name)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
server := rpc.NewServer() *reply = e.mux.NextId()
RegisterProvisioner(server, prov) server := NewServerWithMux(e.mux, *reply)
server.RegisterProvisioner(prov)
*reply = serveSingleConn(server) go server.Serve()
return nil return nil
} }
func (e *EnvironmentServer) Ui(args *interface{}, reply *string) error { func (e *EnvironmentServer) Ui(args *interface{}, reply *uint32) error {
ui := e.env.Ui() ui := e.env.Ui()
// Wrap it *reply = e.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(e.mux, *reply)
RegisterUi(server, ui) server.RegisterUi(ui)
go server.Serve()
*reply = serveSingleConn(server)
return nil return nil
} }
...@@ -2,7 +2,6 @@ package rpc ...@@ -2,7 +2,6 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
...@@ -69,16 +68,11 @@ func TestEnvironmentRPC(t *testing.T) { ...@@ -69,16 +68,11 @@ func TestEnvironmentRPC(t *testing.T) {
e := &testEnvironment{} e := &testEnvironment{}
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterEnvironment(server, e) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterEnvironment(e)
// Create the client over RPC and run some methods to verify it works eClient := client.Environment()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
eClient := &Environment{client}
// Test Builder // Test Builder
builder, _ := eClient.Builder("foo") builder, _ := eClient.Builder("foo")
......
...@@ -10,32 +10,36 @@ import ( ...@@ -10,32 +10,36 @@ import (
// over an RPC connection. // over an RPC connection.
type hook struct { type hook struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// HookServer wraps a packer.Hook implementation and makes it exportable // HookServer wraps a packer.Hook implementation and makes it exportable
// as part of a Golang RPC server. // as part of a Golang RPC server.
type HookServer struct { type HookServer struct {
hook packer.Hook hook packer.Hook
mux *MuxConn
} }
type HookRunArgs struct { type HookRunArgs struct {
Name string Name string
Data interface{} Data interface{}
RPCAddress string StreamId uint32
}
func Hook(client *rpc.Client) *hook {
return &hook{client}
} }
func (h *hook) Run(name string, ui packer.Ui, comm packer.Communicator, data interface{}) error { func (h *hook) Run(name string, ui packer.Ui, comm packer.Communicator, data interface{}) error {
server := rpc.NewServer() nextId := h.mux.NextId()
RegisterCommunicator(server, comm) server := NewServerWithMux(h.mux, nextId)
RegisterUi(server, ui) server.RegisterCommunicator(comm)
address := serveSingleConn(server) server.RegisterUi(ui)
go server.Serve()
args := &HookRunArgs{name, data, address} args := HookRunArgs{
return h.client.Call("Hook.Run", args, new(interface{})) Name: name,
Data: data,
StreamId: nextId,
}
return h.client.Call("Hook.Run", &args, new(interface{}))
} }
func (h *hook) Cancel() { func (h *hook) Cancel() {
...@@ -46,12 +50,13 @@ func (h *hook) Cancel() { ...@@ -46,12 +50,13 @@ func (h *hook) Cancel() {
} }
func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error { func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error {
client, err := rpcDial(args.RPCAddress) client, err := NewClientWithMux(h.mux, args.StreamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
if err := h.hook.Run(args.Name, &Ui{client}, Communicator(client), args.Data); err != nil { if err := h.hook.Run(args.Name, client.Ui(), client.Communicator(), args.Data); err != nil {
return NewBasicError(err) return NewBasicError(err)
} }
......
...@@ -2,7 +2,6 @@ package rpc ...@@ -2,7 +2,6 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"sync" "sync"
"testing" "testing"
...@@ -14,17 +13,11 @@ func TestHookRPC(t *testing.T) { ...@@ -14,17 +13,11 @@ func TestHookRPC(t *testing.T) {
h := new(packer.MockHook) h := new(packer.MockHook)
// Serve // Serve
server := rpc.NewServer() client, server := testClientServer(t)
RegisterHook(server, h) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterHook(h)
// Create the client over RPC and run some methods to verify it works hClient := client.Hook()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
hClient := Hook(client)
// Test Run // Test Run
ui := &testUi{} ui := &testUi{}
...@@ -60,17 +53,11 @@ func TestHook_cancelWhileRun(t *testing.T) { ...@@ -60,17 +53,11 @@ func TestHook_cancelWhileRun(t *testing.T) {
} }
// Serve // Serve
server := rpc.NewServer() client, server := testClientServer(t)
RegisterHook(server, h) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterHook(h)
// Create the client over RPC and run some methods to verify it works hClient := client.Hook()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
hClient := Hook(client)
// Start the run // Start the run
finished := make(chan struct{}) finished := make(chan struct{})
......
This diff is collapsed.
package rpc
import (
"io"
"net"
"sync"
"testing"
)
func readStream(t *testing.T, s io.Reader) string {
var data [1024]byte
n, err := s.Read(data[:])
if err != nil {
t.Fatalf("err: %s", err)
}
return string(data[0:n])
}
func testMux(t *testing.T) (client *MuxConn, server *MuxConn) {
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("err: %s", err)
}
// Server side
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
conn, err := l.Accept()
l.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
server = NewMuxConn(conn)
}()
// Client side
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Fatalf("err: %s", err)
}
client = NewMuxConn(conn)
// Wait for the server
<-doneCh
return
}
func TestMuxConn(t *testing.T) {
client, server := testMux(t)
defer client.Close()
defer server.Close()
// When the server is done
doneCh := make(chan struct{})
// The server side
go func() {
defer close(doneCh)
s0, err := server.Accept(0)
if err != nil {
t.Fatalf("err: %s", err)
}
s1, err := server.Dial(1)
if err != nil {
t.Fatalf("err: %s", err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
data := readStream(t, s1)
if data != "another" {
t.Fatalf("bad: %#v", data)
}
}()
go func() {
defer wg.Done()
data := readStream(t, s0)
if data != "hello" {
t.Fatalf("bad: %#v", data)
}
}()
wg.Wait()
}()
s0, err := client.Dial(0)
if err != nil {
t.Fatalf("err: %s", err)
}
s1, err := client.Accept(1)
if err != nil {
t.Fatalf("err: %s", err)
}
if _, err := s0.Write([]byte("hello")); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := s1.Write([]byte("another")); err != nil {
t.Fatalf("err: %s", err)
}
// Wait for the server to be done
<-doneCh
}
func TestMuxConn_socketClose(t *testing.T) {
client, server := testMux(t)
defer client.Close()
defer server.Close()
go func() {
_, err := server.Accept(0)
if err != nil {
t.Fatalf("err: %s", err)
}
server.rwc.Close()
}()
s0, err := client.Dial(0)
if err != nil {
t.Fatalf("err: %s", err)
}
var data [1024]byte
_, err = s0.Read(data[:])
if err != io.EOF {
t.Fatalf("err: %s", err)
}
}
func TestMuxConn_clientClosesStreams(t *testing.T) {
client, server := testMux(t)
defer client.Close()
defer server.Close()
go func() {
conn, err := server.Accept(0)
if err != nil {
t.Fatalf("err: %s", err)
}
conn.Close()
}()
s0, err := client.Dial(0)
if err != nil {
t.Fatalf("err: %s", err)
}
var data [1024]byte
_, err = s0.Read(data[:])
if err != io.EOF {
t.Fatalf("err: %s", err)
}
}
func TestMuxConn_serverClosesStreams(t *testing.T) {
client, server := testMux(t)
defer client.Close()
defer server.Close()
go server.Accept(0)
s0, err := client.Dial(0)
if err != nil {
t.Fatalf("err: %s", err)
}
if err := server.Close(); err != nil {
t.Fatalf("err: %s", err)
}
// This should block forever since we never write onto this stream.
var data [1024]byte
_, err = s0.Read(data[:])
if err != io.EOF {
t.Fatalf("err: %s", err)
}
}
func TestMuxConnNextId(t *testing.T) {
client, server := testMux(t)
defer client.Close()
defer server.Close()
a := client.NextId()
b := client.NextId()
if a != 0 || b != 1 {
t.Fatalf("IDs should increment")
}
}
...@@ -9,12 +9,15 @@ import ( ...@@ -9,12 +9,15 @@ import (
// executed over an RPC connection. // executed over an RPC connection.
type postProcessor struct { type postProcessor struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// PostProcessorServer wraps a packer.PostProcessor implementation and makes it // PostProcessorServer wraps a packer.PostProcessor implementation and makes it
// exportable as part of a Golang RPC server. // exportable as part of a Golang RPC server.
type PostProcessorServer struct { type PostProcessorServer struct {
p packer.PostProcessor client *rpc.Client
mux *MuxConn
p packer.PostProcessor
} }
type PostProcessorConfigureArgs struct { type PostProcessorConfigureArgs struct {
...@@ -22,14 +25,11 @@ type PostProcessorConfigureArgs struct { ...@@ -22,14 +25,11 @@ type PostProcessorConfigureArgs struct {
} }
type PostProcessorProcessResponse struct { type PostProcessorProcessResponse struct {
Err error Err error
Keep bool Keep bool
RPCAddress string StreamId uint32
} }
func PostProcessor(client *rpc.Client) *postProcessor {
return &postProcessor{client}
}
func (p *postProcessor) Configure(raw ...interface{}) (err error) { func (p *postProcessor) Configure(raw ...interface{}) (err error) {
args := &PostProcessorConfigureArgs{Configs: raw} args := &PostProcessorConfigureArgs{Configs: raw}
if cerr := p.client.Call("PostProcessor.Configure", args, &err); cerr != nil { if cerr := p.client.Call("PostProcessor.Configure", args, &err); cerr != nil {
...@@ -40,12 +40,14 @@ func (p *postProcessor) Configure(raw ...interface{}) (err error) { ...@@ -40,12 +40,14 @@ func (p *postProcessor) Configure(raw ...interface{}) (err error) {
} }
func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) { func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
server := rpc.NewServer() nextId := p.mux.NextId()
RegisterArtifact(server, a) server := NewServerWithMux(p.mux, nextId)
RegisterUi(server, ui) server.RegisterArtifact(a)
server.RegisterUi(ui)
go server.Serve()
var response PostProcessorProcessResponse var response PostProcessorProcessResponse
if err := p.client.Call("PostProcessor.PostProcess", serveSingleConn(server), &response); err != nil { if err := p.client.Call("PostProcessor.PostProcess", nextId, &response); err != nil {
return nil, false, err return nil, false, err
} }
...@@ -53,16 +55,16 @@ func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Art ...@@ -53,16 +55,16 @@ func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Art
return nil, false, response.Err return nil, false, response.Err
} }
if response.RPCAddress == "" { if response.StreamId == 0 {
return nil, false, nil return nil, false, nil
} }
client, err := rpcDial(response.RPCAddress) client, err := NewClientWithMux(p.mux, response.StreamId)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
return Artifact(client), response.Keep, nil return client.Artifact(), response.Keep, nil
} }
func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *error) error { func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *error) error {
...@@ -74,19 +76,20 @@ func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply ...@@ -74,19 +76,20 @@ func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply
return nil return nil
} }
func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorProcessResponse) error { func (p *PostProcessorServer) PostProcess(streamId uint32, reply *PostProcessorProcessResponse) error {
client, err := rpcDial(address) client, err := NewClientWithMux(p.mux, streamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
responseAddress := ""
streamId = 0
artifact, keep, err := p.p.PostProcess(&Ui{client}, Artifact(client)) artifactResult, keep, err := p.p.PostProcess(client.Ui(), client.Artifact())
if err == nil && artifact != nil { if err == nil && artifactResult != nil {
server := rpc.NewServer() streamId = p.mux.NextId()
RegisterArtifact(server, artifact) server := NewServerWithMux(p.mux, streamId)
responseAddress = serveSingleConn(server) server.RegisterArtifact(artifactResult)
go server.Serve()
} }
if err != nil { if err != nil {
...@@ -94,9 +97,9 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr ...@@ -94,9 +97,9 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr
} }
*reply = PostProcessorProcessResponse{ *reply = PostProcessorProcessResponse{
Err: err, Err: err,
Keep: keep, Keep: keep,
RPCAddress: responseAddress, StreamId: streamId,
} }
return nil return nil
......
...@@ -2,18 +2,18 @@ package rpc ...@@ -2,18 +2,18 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
var testPostProcessorArtifact = new(testArtifact) var testPostProcessorArtifact = new(packer.MockArtifact)
type TestPostProcessor struct { type TestPostProcessor struct {
configCalled bool configCalled bool
configVal []interface{} configVal []interface{}
ppCalled bool ppCalled bool
ppArtifact packer.Artifact ppArtifact packer.Artifact
ppArtifactId string
ppUi packer.Ui ppUi packer.Ui
} }
...@@ -26,6 +26,7 @@ func (pp *TestPostProcessor) Configure(v ...interface{}) error { ...@@ -26,6 +26,7 @@ func (pp *TestPostProcessor) Configure(v ...interface{}) error {
func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) { func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
pp.ppCalled = true pp.ppCalled = true
pp.ppArtifact = a pp.ppArtifact = a
pp.ppArtifactId = a.Id()
pp.ppUi = ui pp.ppUi = ui
return testPostProcessorArtifact, false, nil return testPostProcessorArtifact, false, nil
} }
...@@ -35,20 +36,16 @@ func TestPostProcessorRPC(t *testing.T) { ...@@ -35,20 +36,16 @@ func TestPostProcessorRPC(t *testing.T) {
p := new(TestPostProcessor) p := new(TestPostProcessor)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterPostProcessor(server, p) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterPostProcessor(p)
// Create the client over RPC and run some methods to verify it works ppClient := client.PostProcessor()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("Error connecting to rpc: %s", err)
}
// Test Configure // Test Configure
config := 42 config := 42
pClient := PostProcessor(client) err := ppClient.Configure(config)
err = pClient.Configure(config)
if err != nil { if err != nil {
t.Fatalf("error: %s", err) t.Fatalf("error: %s", err)
} }
...@@ -62,9 +59,11 @@ func TestPostProcessorRPC(t *testing.T) { ...@@ -62,9 +59,11 @@ func TestPostProcessorRPC(t *testing.T) {
} }
// Test PostProcess // Test PostProcess
a := new(testArtifact) a := &packer.MockArtifact{
IdValue: "ppTestId",
}
ui := new(testUi) ui := new(testUi)
artifact, _, err := pClient.PostProcess(ui, a) artifact, _, err := ppClient.PostProcess(ui, a)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -73,18 +72,18 @@ func TestPostProcessorRPC(t *testing.T) { ...@@ -73,18 +72,18 @@ func TestPostProcessorRPC(t *testing.T) {
t.Fatal("postprocess should be called") t.Fatal("postprocess should be called")
} }
if p.ppArtifact.BuilderId() != "bid" { if p.ppArtifactId != "ppTestId" {
t.Fatal("unknown artifact") t.Fatalf("unknown artifact: %s", p.ppArtifact.Id())
} }
if artifact.BuilderId() != "bid" { if artifact.Id() != "id" {
t.Fatal("unknown result artifact") t.Fatalf("unknown artifact: %s", artifact.Id())
} }
} }
func TestPostProcessor_Implements(t *testing.T) { func TestPostProcessor_Implements(t *testing.T) {
var raw interface{} var raw interface{}
raw = PostProcessor(nil) raw = new(postProcessor)
if _, ok := raw.(packer.PostProcessor); !ok { if _, ok := raw.(packer.PostProcessor); !ok {
t.Fatal("not a postprocessor") t.Fatal("not a postprocessor")
} }
......
...@@ -10,25 +10,20 @@ import ( ...@@ -10,25 +10,20 @@ import (
// executed over an RPC connection. // executed over an RPC connection.
type provisioner struct { type provisioner struct {
client *rpc.Client client *rpc.Client
mux *MuxConn
} }
// ProvisionerServer wraps a packer.Provisioner implementation and makes it // ProvisionerServer wraps a packer.Provisioner implementation and makes it
// exportable as part of a Golang RPC server. // exportable as part of a Golang RPC server.
type ProvisionerServer struct { type ProvisionerServer struct {
p packer.Provisioner p packer.Provisioner
mux *MuxConn
} }
type ProvisionerPrepareArgs struct { type ProvisionerPrepareArgs struct {
Configs []interface{} Configs []interface{}
} }
type ProvisionerProvisionArgs struct {
RPCAddress string
}
func Provisioner(client *rpc.Client) *provisioner {
return &provisioner{client}
}
func (p *provisioner) Prepare(configs ...interface{}) (err error) { func (p *provisioner) Prepare(configs ...interface{}) (err error) {
args := &ProvisionerPrepareArgs{configs} args := &ProvisionerPrepareArgs{configs}
if cerr := p.client.Call("Provisioner.Prepare", args, &err); cerr != nil { if cerr := p.client.Call("Provisioner.Prepare", args, &err); cerr != nil {
...@@ -39,13 +34,13 @@ func (p *provisioner) Prepare(configs ...interface{}) (err error) { ...@@ -39,13 +34,13 @@ func (p *provisioner) Prepare(configs ...interface{}) (err error) {
} }
func (p *provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { func (p *provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// TODO: Error handling nextId := p.mux.NextId()
server := rpc.NewServer() server := NewServerWithMux(p.mux, nextId)
RegisterCommunicator(server, comm) server.RegisterCommunicator(comm)
RegisterUi(server, ui) server.RegisterUi(ui)
go server.Serve()
args := &ProvisionerProvisionArgs{serveSingleConn(server)} return p.client.Call("Provisioner.Provision", nextId, new(interface{}))
return p.client.Call("Provisioner.Provision", args, new(interface{}))
} }
func (p *provisioner) Cancel() { func (p *provisioner) Cancel() {
...@@ -64,16 +59,14 @@ func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error) ...@@ -64,16 +59,14 @@ func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error)
return nil return nil
} }
func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *interface{}) error { func (p *ProvisionerServer) Provision(streamId uint32, reply *interface{}) error {
client, err := rpcDial(args.RPCAddress) client, err := NewClientWithMux(p.mux, streamId)
if err != nil { if err != nil {
return err return NewBasicError(err)
} }
defer client.Close()
comm := Communicator(client) if err := p.p.Provision(client.Ui(), client.Communicator()); err != nil {
ui := &Ui{client}
if err := p.p.Provision(ui, comm); err != nil {
return NewBasicError(err) return NewBasicError(err)
} }
......
...@@ -2,7 +2,6 @@ package rpc ...@@ -2,7 +2,6 @@ package rpc
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
...@@ -12,19 +11,14 @@ func TestProvisionerRPC(t *testing.T) { ...@@ -12,19 +11,14 @@ func TestProvisionerRPC(t *testing.T) {
p := new(packer.MockProvisioner) p := new(packer.MockProvisioner)
// Start the server // Start the server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterProvisioner(server, p) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterProvisioner(p)
// Create the client over RPC and run some methods to verify it works pClient := client.Provisioner()
client, err := rpc.Dial("tcp", address)
if err != nil {
t.Fatalf("err: %s", err)
}
// Test Prepare // Test Prepare
config := 42 config := 42
pClient := Provisioner(client)
pClient.Prepare(config) pClient.Prepare(config)
if !p.PrepCalled { if !p.PrepCalled {
t.Fatal("should be called") t.Fatal("should be called")
...@@ -41,11 +35,6 @@ func TestProvisionerRPC(t *testing.T) { ...@@ -41,11 +35,6 @@ func TestProvisionerRPC(t *testing.T) {
t.Fatal("should be called") t.Fatal("should be called")
} }
p.ProvUi.Say("foo")
if !ui.sayCalled {
t.Fatal("should be called")
}
// Test Cancel // Test Cancel
pClient.Cancel() pClient.Cancel()
if !p.CancelCalled { if !p.CancelCalled {
...@@ -54,5 +43,5 @@ func TestProvisionerRPC(t *testing.T) { ...@@ -54,5 +43,5 @@ func TestProvisionerRPC(t *testing.T) {
} }
func TestProvisioner_Implements(t *testing.T) { func TestProvisioner_Implements(t *testing.T) {
var _ packer.Provisioner = Provisioner(nil) var _ packer.Provisioner = new(provisioner)
} }
package rpc package rpc
import ( import (
"fmt"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io"
"log"
"net/rpc" "net/rpc"
"sync/atomic"
) )
// Registers the appropriate endpoint on an RPC server to serve an var endpointId uint64
// Artifact.
func RegisterArtifact(s *rpc.Server, a packer.Artifact) { const (
s.RegisterName("Artifact", &ArtifactServer{a}) DefaultArtifactEndpoint string = "Artifact"
DefaultBuildEndpoint = "Build"
DefaultBuilderEndpoint = "Builder"
DefaultCacheEndpoint = "Cache"
DefaultCommandEndpoint = "Command"
DefaultCommunicatorEndpoint = "Communicator"
DefaultEnvironmentEndpoint = "Environment"
DefaultHookEndpoint = "Hook"
DefaultPostProcessorEndpoint = "PostProcessor"
DefaultProvisionerEndpoint = "Provisioner"
DefaultUiEndpoint = "Ui"
)
// Server represents an RPC server for Packer. This must be paired on
// the other side with a Client.
type Server struct {
mux *MuxConn
streamId uint32
server *rpc.Server
} }
// Registers the appropriate endpoint on an RPC server to serve a // NewServer returns a new Packer RPC server.
// Packer Build. func NewServer(conn io.ReadWriteCloser) *Server {
func RegisterBuild(s *rpc.Server, b packer.Build) { return NewServerWithMux(NewMuxConn(conn), 0)
s.RegisterName("Build", &BuildServer{b})
} }
// Registers the appropriate endpoint on an RPC server to serve a func NewServerWithMux(mux *MuxConn, streamId uint32) *Server {
// Packer Builder. return &Server{
func RegisterBuilder(s *rpc.Server, b packer.Builder) { mux: mux,
s.RegisterName("Builder", &BuilderServer{b}) streamId: streamId,
server: rpc.NewServer(),
}
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) Close() error {
// Packer Cache. return s.mux.Close()
func RegisterCache(s *rpc.Server, c packer.Cache) {
s.RegisterName("Cache", &CacheServer{c})
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) RegisterArtifact(a packer.Artifact) {
// Packer Command. s.server.RegisterName(DefaultArtifactEndpoint, &ArtifactServer{
func RegisterCommand(s *rpc.Server, c packer.Command) { artifact: a,
s.RegisterName("Command", &CommandServer{c}) })
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) RegisterBuild(b packer.Build) {
// Packer Communicator. s.server.RegisterName(DefaultBuildEndpoint, &BuildServer{
func RegisterCommunicator(s *rpc.Server, c packer.Communicator) { build: b,
s.RegisterName("Communicator", &CommunicatorServer{c}) mux: s.mux,
})
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) RegisterBuilder(b packer.Builder) {
// Packer Environment s.server.RegisterName(DefaultBuilderEndpoint, &BuilderServer{
func RegisterEnvironment(s *rpc.Server, e packer.Environment) { builder: b,
s.RegisterName("Environment", &EnvironmentServer{e}) mux: s.mux,
})
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) RegisterCache(c packer.Cache) {
// Hook. s.server.RegisterName(DefaultCacheEndpoint, &CacheServer{
func RegisterHook(s *rpc.Server, hook packer.Hook) { cache: c,
s.RegisterName("Hook", &HookServer{hook}) })
} }
// Registers the appropriate endpoing on an RPC server to serve a func (s *Server) RegisterCommand(c packer.Command) {
// PostProcessor. s.server.RegisterName(DefaultCommandEndpoint, &CommandServer{
func RegisterPostProcessor(s *rpc.Server, p packer.PostProcessor) { command: c,
s.RegisterName("PostProcessor", &PostProcessorServer{p}) mux: s.mux,
})
} }
// Registers the appropriate endpoint on an RPC server to serve a packer.Provisioner func (s *Server) RegisterCommunicator(c packer.Communicator) {
func RegisterProvisioner(s *rpc.Server, p packer.Provisioner) { s.server.RegisterName(DefaultCommunicatorEndpoint, &CommunicatorServer{
s.RegisterName("Provisioner", &ProvisionerServer{p}) c: c,
mux: s.mux,
})
} }
// Registers the appropriate endpoint on an RPC server to serve a func (s *Server) RegisterEnvironment(b packer.Environment) {
// Packer UI s.server.RegisterName(DefaultEnvironmentEndpoint, &EnvironmentServer{
func RegisterUi(s *rpc.Server, ui packer.Ui) { env: b,
s.RegisterName("Ui", &UiServer{ui}) mux: s.mux,
})
} }
func serveSingleConn(s *rpc.Server) string { func (s *Server) RegisterHook(h packer.Hook) {
l := netListenerInRange(portRangeMin, portRangeMax) s.server.RegisterName(DefaultHookEndpoint, &HookServer{
hook: h,
mux: s.mux,
})
}
// Accept a single connection in a goroutine and then exit func (s *Server) RegisterPostProcessor(p packer.PostProcessor) {
go func() { s.server.RegisterName(DefaultPostProcessorEndpoint, &PostProcessorServer{
defer l.Close() mux: s.mux,
conn, err := l.Accept() p: p,
if err != nil { })
panic(err) }
}
s.ServeConn(conn) func (s *Server) RegisterProvisioner(p packer.Provisioner) {
}() s.server.RegisterName(DefaultProvisionerEndpoint, &ProvisionerServer{
mux: s.mux,
p: p,
})
}
func (s *Server) RegisterUi(ui packer.Ui) {
s.server.RegisterName(DefaultUiEndpoint, &UiServer{
ui: ui,
})
}
// ServeConn serves a single connection over the RPC server. It is up
// to the caller to obtain a proper io.ReadWriteCloser.
func (s *Server) Serve() {
// Accept a connection on stream ID 0, which is always used for
// normal client to server connections.
stream, err := s.mux.Accept(s.streamId)
defer stream.Close()
if err != nil {
log.Printf("[ERR] Error retrieving stream for serving: %s", err)
return
}
s.server.ServeConn(stream)
}
return l.Addr().String() // registerComponent registers a single Packer RPC component onto
// the RPC server. If id is true, then a unique ID number will be appended
// onto the end of the endpoint.
//
// The endpoint name is returned.
func registerComponent(server *rpc.Server, name string, rcvr interface{}, id bool) string {
endpoint := name
if id {
fmt.Sprintf("%s.%d", endpoint, atomic.AddUint64(&endpointId, 1))
}
server.RegisterName(endpoint, rcvr)
return endpoint
} }
...@@ -9,7 +9,8 @@ import ( ...@@ -9,7 +9,8 @@ import (
// An implementation of packer.Ui where the Ui is actually executed // An implementation of packer.Ui where the Ui is actually executed
// over an RPC connection. // over an RPC connection.
type Ui struct { type Ui struct {
client *rpc.Client client *rpc.Client
endpoint string
} }
// UiServer wraps a packer.Ui implementation and makes it exportable // UiServer wraps a packer.Ui implementation and makes it exportable
......
package rpc package rpc
import ( import (
"net/rpc"
"reflect" "reflect"
"testing" "testing"
) )
...@@ -52,17 +51,12 @@ func TestUiRPC(t *testing.T) { ...@@ -52,17 +51,12 @@ func TestUiRPC(t *testing.T) {
ui := new(testUi) ui := new(testUi)
// Start the RPC server // Start the RPC server
server := rpc.NewServer() client, server := testClientServer(t)
RegisterUi(server, ui) defer client.Close()
address := serveSingleConn(server) defer server.Close()
server.RegisterUi(ui)
// Create the client over RPC and run some methods to verify it works uiClient := client.Ui()
client, err := rpc.Dial("tcp", address)
if err != nil {
panic(err)
}
uiClient := &Ui{client}
// Basic error and say tests // Basic error and say tests
result, err := uiClient.Ask("query") result, err := uiClient.Ask("query")
......
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(chroot.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(chroot.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(ebs.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(ebs.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(instance.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(instance.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(digitalocean.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(digitalocean.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(docker.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(docker.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(openstack.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(openstack.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(qemu.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(qemu.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(virtualbox.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(virtualbox.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeBuilder(new(vmware.Builder)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(vmware.Builder))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeCommand(new(build.Command)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterCommand(new(build.Command))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeCommand(new(fix.Command)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterCommand(new(fix.Command))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeCommand(new(inspect.Command)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterCommand(new(inspect.Command))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeCommand(new(validate.Command)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterCommand(new(validate.Command))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServePostProcessor(new(vagrant.PostProcessor)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterPostProcessor(new(vagrant.PostProcessor))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServePostProcessor(new(vsphere.PostProcessor)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterPostProcessor(new(vsphere.PostProcessor))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(ansiblelocal.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(ansiblelocal.Provisioner))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(chefsolo.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(chefsolo.Provisioner))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(file.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(file.Provisioner))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(puppetmasterless.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(puppetmasterless.Provisioner))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(saltmasterless.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(saltmasterless.Provisioner))
server.Serve()
} }
...@@ -6,5 +6,10 @@ import ( ...@@ -6,5 +6,10 @@ import (
) )
func main() { func main() {
plugin.ServeProvisioner(new(shell.Provisioner)) server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterProvisioner(new(shell.Provisioner))
server.Serve()
} }
Markdown is supported
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