Crazy things with RPC servers and stuff

This commit is contained in:
Mitchell Hashimoto 2013-05-03 23:55:08 -07:00
parent 0985d26167
commit f0a09ffa6b
3 changed files with 205 additions and 0 deletions

58
packer/rpc/build.go Normal file
View File

@ -0,0 +1,58 @@
package rpc
import (
"github.com/mitchellh/packer/packer"
"net/rpc"
)
// An implementation of packer.Build where the build is actually executed
// over an RPC connection.
type Build struct {
client *rpc.Client
}
// BuildServer wraps a packer.Build implementation and makes it exportable
// as part of a Golang RPC server.
type BuildServer struct {
build packer.Build
}
type BuildPrepareArgs interface{}
type BuildRunArgs struct {
UiRPCAddress string
}
func (b *Build) Prepare() {
b.client.Call("Build.Prepare", new(interface{}), new(interface{}))
}
func (b *Build) Run(ui packer.Ui) {
// Create and start the server for the UI
server := NewServer()
server.RegisterUi(ui)
server.Start()
defer server.Stop()
args := &BuildRunArgs{server.Address()}
b.client.Call("Build.Run", args, new(interface{}))
}
func (b *BuildServer) Prepare(args *BuildPrepareArgs, reply *interface{}) error {
b.build.Prepare()
*reply = nil
return nil
}
func (b *BuildServer) Run(args *BuildRunArgs, reply *interface{}) error {
client, err := rpc.Dial("tcp", args.UiRPCAddress)
if err != nil {
return err
}
b.build.Run(&Ui{client})
*reply = nil
return nil
}

70
packer/rpc/build_test.go Normal file
View File

@ -0,0 +1,70 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"testing"
)
type testBuild struct {
prepareCalled bool
runCalled bool
runUi packer.Ui
}
func (b *testBuild) Prepare() {
b.prepareCalled = true
}
func (b *testBuild) Run(ui packer.Ui) {
b.runCalled = true
b.runUi = ui
}
func TestBuildRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the UI to test
b := new(testBuild)
bServer := &BuildServer{b}
// Start the RPC server
readyChan := make(chan int)
stopChan := make(chan int)
defer func() { stopChan <- 1 }()
go testRPCServer(":1234", "Build", bServer, 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)
}
// Test Prepare
bClient := &Build{client}
bClient.Prepare()
assert.True(b.prepareCalled, "prepare should be called")
// Test Run
ui := new(testUi)
bClient.Run(ui)
assert.True(b.runCalled, "run should be called")
// Test the UI given to run, which should be fully functional
if b.runCalled {
b.runUi.Say("format")
assert.True(ui.sayCalled, "say should be called")
assert.Equal(ui.sayFormat, "format", "format should be correct")
}
}
func TestBuild_ImplementsBuild(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var realBuild packer.Build
b := &Build{nil}
assert.Implementor(b, &realBuild, "should be a Build")
}

77
packer/rpc/server.go Normal file
View File

@ -0,0 +1,77 @@
package rpc
import (
"errors"
"github.com/mitchellh/packer/packer"
"net"
"net/rpc"
)
// A Server is a Golang RPC server that has helper methods for automatically
// setting up the endpoints for Packer interfaces.
type Server struct {
server *rpc.Server
started bool
doneChan chan bool
}
// Creates and returns a new Server.
func NewServer() *Server {
return &Server{
server: rpc.NewServer(),
started: false,
}
}
func (s *Server) Address() string {
return ":2345"
}
func (s *Server) RegisterUi(ui packer.Ui) {
s.server.RegisterName("Ui", &UiServer{ui})
}
func (s *Server) Start() error {
if s.started {
return errors.New("Server already started.")
}
// TODO: Address
address := ":2345"
// Mark that we started and setup the channel we'll use to mark exits
s.started = true
s.doneChan = make(chan bool)
// Start the TCP listener and a goroutine responsible for cleaning up the
// listener.
listener, _ := net.Listen("tcp", address)
go func() {
<-s.doneChan
listener.Close()
}()
// Start accepting connections
go func() {
for {
conn, err := listener.Accept()
if err != nil {
break
}
go s.server.ServeConn(conn)
}
}()
return nil
}
func (s *Server) Stop() {
if s.started {
// TODO: There is a race condition here, we need to wait for
// the listener to REALLY close.
s.doneChan <- true
s.started = false
s.doneChan = nil
}
}