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:
150
storage/file.go
Normal file
150
storage/file.go
Normal 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
218
storage/file_test.go
Normal 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
23
storage/storage.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user