Storage has now it's own package.

Remove global `sessions` map and use configured storage.
Add a `types` package so both `pwd` and `storage` can access without
circular dependencies.
Now the session is prepared when requested and not on load.
This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-06-14 18:35:21 -03:00
parent 2eff799c58
commit e9911abf94
22 changed files with 814 additions and 499 deletions

150
storage/file.go Normal file
View File

@@ -0,0 +1,150 @@
package storage
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"github.com/play-with-docker/play-with-docker/pwd/types"
)
type storage struct {
rw sync.Mutex
path string
db map[string]*types.Session
}
func (store *storage) SessionGet(sessionId string) (*types.Session, error) {
store.rw.Lock()
defer store.rw.Unlock()
s, found := store.db[sessionId]
if !found {
return nil, fmt.Errorf("%s", notFound)
}
return s, nil
}
func (store *storage) SessionPut(s *types.Session) error {
store.rw.Lock()
defer store.rw.Unlock()
store.db[s.Id] = s
return store.save()
}
func (store *storage) InstanceFindByIP(ip string) (*types.Instance, error) {
store.rw.Lock()
defer store.rw.Unlock()
for _, s := range store.db {
for _, i := range s.Instances {
if i.IP == ip {
return i, nil
}
}
}
return nil, fmt.Errorf("%s", notFound)
}
func (store *storage) InstanceFindByAlias(sessionPrefix, alias string) (*types.Instance, error) {
store.rw.Lock()
defer store.rw.Unlock()
for id, s := range store.db {
if strings.HasPrefix(id, sessionPrefix) {
for _, i := range s.Instances {
if i.Alias == alias {
return i, nil
}
}
}
}
return nil, fmt.Errorf("%s", notFound)
}
func (store *storage) SessionCount() (int, error) {
store.rw.Lock()
defer store.rw.Unlock()
return len(store.db), nil
}
func (store *storage) InstanceCount() (int, error) {
store.rw.Lock()
defer store.rw.Unlock()
var ins int
for _, s := range store.db {
ins += len(s.Instances)
}
return ins, nil
}
func (store *storage) ClientCount() (int, error) {
store.rw.Lock()
defer store.rw.Unlock()
var cli int
for _, s := range store.db {
cli += len(s.Clients)
}
return cli, nil
}
func (store *storage) SessionDelete(sessionId string) error {
store.rw.Lock()
defer store.rw.Unlock()
delete(store.db, sessionId)
return store.save()
}
func (store *storage) load() error {
file, err := os.Open(store.path)
if err == nil {
decoder := json.NewDecoder(file)
err = decoder.Decode(&store.db)
if err != nil {
return err
}
} else {
store.db = map[string]*types.Session{}
}
file.Close()
return nil
}
func (store *storage) save() error {
file, err := os.Create(store.path)
if err != nil {
return err
}
defer file.Close()
encoder := json.NewEncoder(file)
err = encoder.Encode(&store.db)
return err
}
func NewFileStorage(path string) (StorageApi, error) {
s := &storage{path: path}
err := s.load()
if err != nil {
return nil, err
}
return s, nil
}

218
storage/file_test.go Normal file
View File

@@ -0,0 +1,218 @@
package storage
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"testing"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/stretchr/testify/assert"
)
func TestSessionPut(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
s := &types.Session{Id: "a session"}
err = storage.SessionPut(s)
assert.Nil(t, err)
var loadedSessions map[string]*types.Session
expectedSessions := map[string]*types.Session{}
expectedSessions[s.Id] = s
file, err := os.Open(tmpfile.Name())
assert.Nil(t, err)
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&loadedSessions)
assert.Nil(t, err)
assert.EqualValues(t, expectedSessions, loadedSessions)
}
func TestSessionGet(t *testing.T) {
expectedSession := &types.Session{Id: "session1"}
sessions := map[string]*types.Session{}
sessions[expectedSession.Id] = expectedSession
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
encoder := json.NewEncoder(tmpfile)
err = encoder.Encode(&sessions)
assert.Nil(t, err)
tmpfile.Close()
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
_, err = storage.SessionGet("bad id")
assert.True(t, NotFound(err))
loadedSession, err := storage.SessionGet("session1")
assert.Nil(t, err)
assert.Equal(t, expectedSession, loadedSession)
}
func TestInstanceFindByIP(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
i1 := &types.Instance{Name: "i1", IP: "10.0.0.1"}
i2 := &types.Instance{Name: "i2", IP: "10.1.0.1"}
s1 := &types.Session{Id: "session1", Instances: map[string]*types.Instance{"i1": i1}}
s2 := &types.Session{Id: "session2", Instances: map[string]*types.Instance{"i2": i2}}
err = storage.SessionPut(s1)
assert.Nil(t, err)
err = storage.SessionPut(s2)
assert.Nil(t, err)
foundInstance, err := storage.InstanceFindByIP("10.0.0.1")
assert.Nil(t, err)
assert.Equal(t, i1, foundInstance)
foundInstance, err = storage.InstanceFindByIP("10.1.0.1")
assert.Nil(t, err)
assert.Equal(t, i2, foundInstance)
foundInstance, err = storage.InstanceFindByIP("192.168.0.1")
assert.True(t, NotFound(err))
assert.Nil(t, foundInstance)
}
func TestInstanceFindByAlias(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
i1 := &types.Instance{Name: "i1", Alias: "foo", IP: "10.0.0.1"}
i2 := &types.Instance{Name: "i2", Alias: "foo", IP: "10.1.0.1"}
s1 := &types.Session{Id: "session1", Instances: map[string]*types.Instance{"i1": i1}}
s2 := &types.Session{Id: "session2", Instances: map[string]*types.Instance{"i2": i2}}
err = storage.SessionPut(s1)
assert.Nil(t, err)
err = storage.SessionPut(s2)
assert.Nil(t, err)
foundInstance, err := storage.InstanceFindByAlias("session1", "foo")
assert.Nil(t, err)
assert.Equal(t, i1, foundInstance)
foundInstance, err = storage.InstanceFindByAlias("session2", "foo")
assert.Nil(t, err)
assert.Equal(t, i2, foundInstance)
foundInstance, err = storage.InstanceFindByAlias("session1", "bar")
assert.True(t, NotFound(err))
assert.Nil(t, foundInstance)
foundInstance, err = storage.InstanceFindByAlias("session3", "foo")
assert.True(t, NotFound(err))
assert.Nil(t, foundInstance)
}
func TestCounts(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
c1 := &types.Client{}
i1 := &types.Instance{Name: "i1", Alias: "foo", IP: "10.0.0.1"}
i2 := &types.Instance{Name: "i2", Alias: "foo", IP: "10.1.0.1"}
s1 := &types.Session{Id: "session1", Instances: map[string]*types.Instance{"i1": i1}}
s2 := &types.Session{Id: "session2", Instances: map[string]*types.Instance{"i2": i2}}
s3 := &types.Session{Id: "session3", Clients: []*types.Client{c1}}
err = storage.SessionPut(s1)
assert.Nil(t, err)
err = storage.SessionPut(s2)
assert.Nil(t, err)
err = storage.SessionPut(s3)
assert.Nil(t, err)
num, err := storage.SessionCount()
assert.Nil(t, err)
assert.Equal(t, 3, num)
num, err = storage.InstanceCount()
assert.Nil(t, err)
assert.Equal(t, 2, num)
num, err = storage.ClientCount()
assert.Nil(t, err)
assert.Equal(t, 1, num)
}
func TestSessionDelete(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
s1 := &types.Session{Id: "session1"}
err = storage.SessionPut(s1)
assert.Nil(t, err)
found, err := storage.SessionGet(s1.Id)
assert.Nil(t, err)
assert.Equal(t, s1, found)
err = storage.SessionDelete(s1.Id)
assert.Nil(t, err)
found, err = storage.SessionGet(s1.Id)
assert.True(t, NotFound(err))
assert.Nil(t, found)
}

23
storage/storage.go Normal file
View File

@@ -0,0 +1,23 @@
package storage
import "github.com/play-with-docker/play-with-docker/pwd/types"
const notFound = "NotFound"
func NotFound(e error) bool {
return e.Error() == notFound
}
type StorageApi interface {
SessionGet(sessionId string) (*types.Session, error)
SessionPut(*types.Session) error
SessionCount() (int, error)
SessionDelete(sessionId string) error
InstanceFindByAlias(sessionPrefix, alias string) (*types.Instance, error)
// Should have the session id too, soon
InstanceFindByIP(ip string) (*types.Instance, error)
InstanceCount() (int, error)
ClientCount() (int, error)
}