Crazy things with RPC servers and stuff
This commit is contained in:
parent
0985d26167
commit
f0a09ffa6b
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue