From 2c8b198049ad8fef75fa54c1b93e683e2ca36bc0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 9 Jun 2013 19:25:48 -0700 Subject: [PATCH] packer/rpc: Support Cache interface --- packer/cache.go | 6 +-- packer/cache_test.go | 6 +-- packer/rpc/cache.go | 77 +++++++++++++++++++++++++++++++++++ packer/rpc/cache_test.go | 86 ++++++++++++++++++++++++++++++++++++++++ packer/rpc/server.go | 6 +++ 5 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 packer/rpc/cache.go create mode 100644 packer/rpc/cache_test.go diff --git a/packer/cache.go b/packer/cache.go index 3753b9be1..6a367253a 100644 --- a/packer/cache.go +++ b/packer/cache.go @@ -14,7 +14,7 @@ type Cache interface { // the lock is held. // // The cache will block and wait for the lock. - Lock(string) (string, error) + Lock(string) string // Unlock will unlock a certain cache key. Be very careful that this // is only called once per lock obtained. @@ -38,12 +38,12 @@ type FileCache struct { rw map[string]*sync.RWMutex } -func (f *FileCache) Lock(key string) (string, error) { +func (f *FileCache) Lock(key string) string { hashKey := f.hashKey(key) rw := f.rwLock(hashKey) rw.Lock() - return filepath.Join(f.CacheDir, hashKey), nil + return filepath.Join(f.CacheDir, hashKey) } func (f *FileCache) Unlock(key string) { diff --git a/packer/cache_test.go b/packer/cache_test.go index 1ef63a15b..d4c3a1c69 100644 --- a/packer/cache_test.go +++ b/packer/cache_test.go @@ -22,11 +22,7 @@ func TestFileCache(t *testing.T) { defer os.RemoveAll(cacheDir) cache := &FileCache{CacheDir: cacheDir} - path, err := cache.Lock("foo") - if err != nil { - t.Fatalf("error locking: %s", err) - } - + path := cache.Lock("foo") err = ioutil.WriteFile(path, []byte("data"), 0666) if err != nil { t.Fatalf("error writing: %s", err) diff --git a/packer/rpc/cache.go b/packer/rpc/cache.go new file mode 100644 index 000000000..73b154cb6 --- /dev/null +++ b/packer/rpc/cache.go @@ -0,0 +1,77 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// An implementation of packer.Cache where the cache is actually executed +// over an RPC connection. +type cache struct { + client *rpc.Client +} + +// CacheServer wraps a packer.Cache implementation and makes it exportable +// as part of a Golang RPC server. +type CacheServer struct { + cache packer.Cache +} + +func Cache(client *rpc.Client) *cache { + return &cache{client} +} + +type CacheRLockResponse struct { + Path string + Exists bool +} + +func (c *cache) Lock(key string) (result string) { + if err := c.client.Call("Cache.Lock", key, &result); err != nil { + panic(err) + } + + return +} + +func (c *cache) RLock(key string) (string, bool) { + var result CacheRLockResponse + if err := c.client.Call("Cache.RLock", key, &result); err != nil { + panic(err) + } + + return result.Path, result.Exists +} + +func (c *cache) Unlock(key string) { + if err := c.client.Call("Cache.Unlock", key, new(interface{})); err != nil { + panic(err) + } +} + +func (c *cache) RUnlock(key string) { + if err := c.client.Call("Cache.RUnlock", key, new(interface{})); err != nil { + panic(err) + } +} + +func (c *CacheServer) Lock(key string, result *string) error { + *result = c.cache.Lock(key) + return nil +} + +func (c *CacheServer) Unlock(key string, result *interface{}) error { + c.cache.Unlock(key) + return nil +} + +func (c *CacheServer) RLock(key string, result *CacheRLockResponse) error { + path, exists := c.cache.RLock(key) + *result = CacheRLockResponse{path, exists} + return nil +} + +func (c *CacheServer) RUnlock(key string, result *interface{}) error { + c.cache.RUnlock(key) + return nil +} diff --git a/packer/rpc/cache_test.go b/packer/rpc/cache_test.go new file mode 100644 index 000000000..9c74de74e --- /dev/null +++ b/packer/rpc/cache_test.go @@ -0,0 +1,86 @@ +package rpc + +import ( + "cgl.tideland.biz/asserts" + "github.com/mitchellh/packer/packer" + "net/rpc" + "testing" +) + +type testCache struct { + lockCalled bool + lockKey string + unlockCalled bool + unlockKey string + rlockCalled bool + rlockKey string + runlockCalled bool + runlockKey string +} + +func (t *testCache) Lock(key string) string { + t.lockCalled = true + t.lockKey = key + return "foo" +} + +func (t *testCache) RLock(key string) (string, bool) { + t.rlockCalled = true + t.rlockKey = key + return "foo", true +} + +func (t *testCache) Unlock(key string) { + t.unlockCalled = true + t.unlockKey = key +} + +func (t *testCache) RUnlock(key string) { + t.runlockCalled = true + t.runlockKey = key +} + +func TestCache_Implements(t *testing.T) { + var raw interface{} + raw = Cache(nil) + if _, ok := raw.(packer.Cache); !ok { + t.Fatal("Cache must be a cache.") + } +} + +func TestCacheRPC(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Create the interface to test + c := new(testCache) + + // Start the server + server := rpc.NewServer() + RegisterCache(server, c) + address := serveSingleConn(server) + + // Create the client over RPC and run some methods to verify it works + rpcClient, err := rpc.Dial("tcp", address) + assert.Nil(err, "should be able to connect") + client := Cache(rpcClient) + + // Test Lock + client.Lock("foo") + assert.True(c.lockCalled, "should be called") + assert.Equal(c.lockKey, "foo", "should have proper key") + + // Test Unlock + client.Unlock("foo") + assert.True(c.unlockCalled, "should be called") + assert.Equal(c.unlockKey, "foo", "should have proper key") + + // Test RLock + client.RLock("foo") + assert.True(c.rlockCalled, "should be called") + assert.Equal(c.rlockKey, "foo", "should have proper key") + + // Test RUnlock + client.RUnlock("foo") + assert.True(c.runlockCalled, "should be called") + assert.Equal(c.runlockKey, "foo", "should have proper key") +} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index 6767b858d..9117582e3 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -23,6 +23,12 @@ func RegisterBuilder(s *rpc.Server, b packer.Builder) { s.RegisterName("Builder", &BuilderServer{b}) } +// Registers the appropriate endpoint on an RPC server to serve a +// Packer Cache. +func RegisterCache(s *rpc.Server, c packer.Cache) { + s.RegisterName("Cache", &CacheServer{c}) +} + // Registers the appropriate endpoint on an RPC server to serve a // Packer Command. func RegisterCommand(s *rpc.Server, c packer.Command) {