From 8dea720627283031ebea6bd72f9fbd197cf77f9f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 May 2013 15:49:15 -0700 Subject: [PATCH] rpc.Ui --- packer/rpc/environment.go | 18 ++++++++++++++++ packer/rpc/rpc_test.go | 43 +++++++++++++++++++++++++++++++++++++ packer/rpc/ui.go | 35 ++++++++++++++++++++++++++++++ packer/rpc/ui_test.go | 45 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 packer/rpc/environment.go create mode 100644 packer/rpc/rpc_test.go create mode 100644 packer/rpc/ui.go create mode 100644 packer/rpc/ui_test.go diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go new file mode 100644 index 000000000..a26b8d7a2 --- /dev/null +++ b/packer/rpc/environment.go @@ -0,0 +1,18 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// A EnvironmentClient is an implementation of the packer.Environment interface +// where the actual environment is executed over an RPC connection. +type EnvironmentClient struct { + client *rpc.Client +} + +// A EnvironmentServer wraps a packer.Environment and makes it exportable +// as part of a Golang RPC server. +type EnvironmentServer struct { + env packer.Environment +} diff --git a/packer/rpc/rpc_test.go b/packer/rpc/rpc_test.go new file mode 100644 index 000000000..04e7eb424 --- /dev/null +++ b/packer/rpc/rpc_test.go @@ -0,0 +1,43 @@ +package rpc + +import ( + "net" + "net/rpc" +) + +// This starts a RPC server for the given interface listening on the +// given address. The RPC server is ready when "readyChan" receives a message +// and the RPC server will quit when "stopChan" receives a message. +// +// This function should be run in a goroutine. +func testRPCServer(laddr string, name string, iface interface{}, readyChan chan int, stopChan <-chan int) { + listener, err := net.Listen("tcp", laddr) + if err != nil { + panic(err) + } + + // Close the listener when we exit so that the RPC server ends + defer listener.Close() + + // Start the RPC server + server := rpc.NewServer() + server.RegisterName(name, iface) + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + // If there is an error, just ignore it. + break + } + + go server.ServeConn(conn) + } + }() + + // We're ready! + readyChan <- 1 + + // Block on waiting to receive from the channel + <-stopChan +} diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go new file mode 100644 index 000000000..ed5450285 --- /dev/null +++ b/packer/rpc/ui.go @@ -0,0 +1,35 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// An implementation of packer.Ui where the Ui is actually executed +// over an RPC connection. +type Ui struct { + client *rpc.Client +} + +// UiServer wraps a packer.Ui implementation and makes it exportable +// as part of a Golang RPC server. +type UiServer struct { + ui packer.Ui +} + +type UiSayArgs struct { + Format string + Vars []interface{} +} + +func (u *Ui) Say(format string, a ...interface{}) { + args := &UiSayArgs{format, a} + u.client.Call("Ui.Say", args, new(interface{})) +} + +func (u *UiServer) Say(args *UiSayArgs, reply *interface{}) error { + u.ui.Say(args.Format, args.Vars...) + + *reply = nil + return nil +} diff --git a/packer/rpc/ui_test.go b/packer/rpc/ui_test.go new file mode 100644 index 000000000..7975d0217 --- /dev/null +++ b/packer/rpc/ui_test.go @@ -0,0 +1,45 @@ +package rpc + +import ( + "cgl.tideland.biz/asserts" + "net/rpc" + "testing" +) + +type testUi struct { + sayCalled bool + sayFormat string + sayVars []interface{} +} + +func (u *testUi) Say(format string, a ...interface{}) { + u.sayCalled = true + u.sayFormat = format + u.sayVars = a +} + +func TestUiRPC(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Create the UI to test + ui := new(testUi) + uiServer := &UiServer{ui} + + // Start the RPC server + readyChan := make(chan int) + stopChan := make(chan int) + defer func() { stopChan <- 1 }() + go testRPCServer(":1234", "Ui", uiServer, readyChan, stopChan) + <-readyChan + + // Create the client over RPC and run some methods to verify it works + client, err := rpc.Dial("tcp", ":1234") + if err != nil { + panic(err) + } + + uiClient := &Ui{client} + uiClient.Say("format", "arg0", 42) + + assert.Equal(ui.sayFormat, "format", "format should be correct") +}