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