Multiple playgrounds support (#215)

* Add Playground struct and basic support for creating it and retrieving
it

* Add missing functions in pwd mock

* Get playground from request domain and validate it exists. If valid set
it on the newly created session.

* Move playground specific configurations to the playground struct and use
it everytime we need that conf.

* Don't allow to specify a duration bigger that the allowed in the
playground
This commit is contained in:
Jonathan Leibiusky
2017-11-14 15:50:04 -03:00
committed by GitHub
parent 3dee0d3f0b
commit 3f5b3882dd
24 changed files with 784 additions and 159 deletions

View File

@@ -43,7 +43,9 @@ func TestClientNew(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
client := p.ClientNew("foobar", session)
@@ -81,8 +83,9 @@ func TestClientCount(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew("", time.Hour, "", "", "")
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
p.ClientNew("foobar", session)
@@ -122,8 +125,9 @@ func TestClientResizeViewPort(t *testing.T) {
_e.M.On("Emit", event.INSTANCE_VIEWPORT_RESIZE, "aaaabbbbcccc", []interface{}{uint(80), uint(24)}).Return()
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew("", time.Hour, "", "", "")
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
client := p.ClientNew("foobar", session)
_s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil)

View File

@@ -70,7 +70,11 @@ func TestInstanceNew(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar", DefaultDinDInstanceImage: "franela/dind"}
_s.On("PlaygroundGet", "foobar").Return(playground, nil)
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
expectedInstance := types.Instance{
@@ -78,7 +82,7 @@ func TestInstanceNew(t *testing.T) {
Hostname: "node1",
IP: "10.0.0.1",
RoutableIP: "10.0.0.1",
Image: config.GetDindImageName(),
Image: "franela/dind",
SessionId: session.Id,
SessionHost: session.Host,
ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}),
@@ -138,7 +142,8 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
@@ -207,7 +212,8 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
expectedInstance := types.Instance{

View File

@@ -13,7 +13,7 @@ type Mock struct {
mock.Mock
}
func (m *Mock) SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) {
func (m *Mock) SessionNew(playground *types.Playground, userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) {
args := m.Called(duration, stack, stackName, imageName)
return args.Get(0).(*types.Session), args.Error(1)
}
@@ -119,7 +119,27 @@ func (m *Mock) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*t
args := m.Called(loginRequest, user)
return args.Get(0).(*types.User), args.Error(1)
}
func (m *Mock) UserGet(id string) (*types.User, error) {
args := m.Called(id)
return args.Get(0).(*types.User), args.Error(1)
}
func (m *Mock) PlaygroundNew(playground types.Playground) (*types.Playground, error) {
args := m.Called(playground)
return args.Get(0).(*types.Playground), args.Error(1)
}
func (m *Mock) PlaygroundGet(id string) *types.Playground {
args := m.Called(id)
return args.Get(0).(*types.Playground)
}
func (m *Mock) PlaygroundFindByDomain(domain string) *types.Playground {
args := m.Called(domain)
return args.Get(0).(*types.Playground)
}
func (m *Mock) PlaygroundList() ([]*types.Playground, error) {
args := m.Called()
return args.Get(0).([]*types.Playground), args.Error(1)
}

36
pwd/playground.go Normal file
View File

@@ -0,0 +1,36 @@
package pwd
import (
"log"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/twinj/uuid"
)
func (p *pwd) PlaygroundNew(playground types.Playground) (*types.Playground, error) {
playground.Id = uuid.NewV5(uuid.NameSpaceURL, uuid.Name(playground.Domain)).String()
if err := p.storage.PlaygroundPut(&playground); err != nil {
log.Printf("Error saving playground %s. Got: %v\n", playground.Id, err)
return nil, err
}
return &playground, nil
}
func (p *pwd) PlaygroundGet(id string) *types.Playground {
if playground, err := p.storage.PlaygroundGet(id); err != nil {
log.Printf("Error retrieving playground %s. Got: %v\n", id, err)
return nil
} else {
return playground
}
}
func (p *pwd) PlaygroundFindByDomain(domain string) *types.Playground {
id := uuid.NewV5(uuid.NameSpaceURL, uuid.Name(domain)).String()
return p.PlaygroundGet(id)
}
func (p *pwd) PlaygroundList() ([]*types.Playground, error) {
return p.storage.PlaygroundGetAll()
}

152
pwd/playground_test.go Normal file
View File

@@ -0,0 +1,152 @@
package pwd
import (
"testing"
"time"
"github.com/play-with-docker/play-with-docker/docker"
"github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/twinj/uuid"
)
func TestPlaygroundNew(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
expectedPlayground.Id = uuid.NewV5(uuid.NameSpaceURL, uuid.Name("localhost")).String()
assert.Equal(t, expectedPlayground, *playground)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundGet(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
_s.On("PlaygroundGet", playground.Id).Return(playground, nil)
playground2 := p.PlaygroundGet(playground.Id)
assert.NotNil(t, playground2)
assert.Equal(t, *playground, *playground2)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundFindByDomain(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
_s.On("PlaygroundGet", playground.Id).Return(playground, nil)
playground2 := p.PlaygroundFindByDomain("localhost")
assert.NotNil(t, playground2)
assert.Equal(t, *playground, *playground2)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundList(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
pd := types.Playground{Domain: "localhost1", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
p1, e := p.PlaygroundNew(pd)
assert.Nil(t, e)
assert.NotNil(t, p1)
pd = types.Playground{Domain: "localhost2", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
p2, e := p.PlaygroundNew(pd)
assert.Nil(t, e)
assert.NotNil(t, p2)
_s.On("PlaygroundGetAll").Return([]*types.Playground{p1, p2}, nil)
received, err := p.PlaygroundList()
assert.Nil(t, err)
assert.NotNil(t, received)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}

View File

@@ -72,7 +72,7 @@ func SessionNotEmpty(e error) bool {
}
type PWDApi interface {
SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error)
SessionNew(playground *types.Playground, userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error)
SessionClose(session *types.Session) error
SessionGetSmallestViewPort(sessionId string) types.ViewPort
SessionDeployStack(session *types.Session) error
@@ -98,6 +98,11 @@ type PWDApi interface {
UserGetLoginRequest(id string) (*types.LoginRequest, error)
UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error)
UserGet(id string) (*types.User, error)
PlaygroundNew(playground types.Playground) (*types.Playground, error)
PlaygroundGet(id string) *types.Playground
PlaygroundFindByDomain(domain string) *types.Playground
PlaygroundList() ([]*types.Playground, error)
}
func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd {

View File

@@ -44,7 +44,7 @@ type SessionSetupInstanceConf struct {
Tls bool `json:"tls"`
}
func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName, imageName string) (*types.Session, error) {
func (p *pwd) SessionNew(playground *types.Playground, userId string, duration time.Duration, stack, stackName, imageName string) (*types.Session, error) {
defer observeAction("SessionNew", time.Now())
s := &types.Session{}
@@ -54,6 +54,7 @@ func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName
s.Ready = true
s.Stack = stack
s.UserId = userId
s.PlaygroundId = playground.Id
if s.Stack != "" {
s.Ready = false

View File

@@ -10,6 +10,7 @@ import (
"github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -45,7 +46,8 @@ func TestSessionNew(t *testing.T) {
before := time.Now()
s, e := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar"}
s, e := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, e)
assert.NotNil(t, s)
@@ -56,12 +58,13 @@ func TestSessionNew(t *testing.T) {
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
assert.True(t, s.Ready)
s, _ = p.SessionNew("", time.Hour, "stackPath", "stackName", "imageName")
s, _ = p.SessionNew(playground, "", time.Hour, "stackPath", "stackName", "imageName")
assert.Equal(t, "stackPath", s.Stack)
assert.Equal(t, "stackName", s.StackName)
assert.Equal(t, "imageName", s.ImageName)
assert.Equal(t, "localhost", s.Host)
assert.Equal(t, playground.Id, s.PlaygroundId)
assert.False(t, s.Ready)
_d.AssertExpectations(t)

83
pwd/types/playground.go Normal file
View File

@@ -0,0 +1,83 @@
package types
import (
"strconv"
"time"
)
type PlaygroundExtras map[string]interface{}
func (e PlaygroundExtras) Get(name string) (interface{}, bool) {
v, f := e[name]
return v, f
}
func (e PlaygroundExtras) GetInt(name string) (int, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return r, ok
} else if r, ok := v.(float64); ok {
return int(r), ok
} else if r, ok := v.(string); ok {
if v, err := strconv.Atoi(r); err != nil {
return 0, false
} else {
return v, true
}
}
return v.(int), f
} else {
return 0, f
}
}
func (e PlaygroundExtras) GetString(name string) (string, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return strconv.Itoa(r), ok
} else if r, ok := v.(float64); ok {
return strconv.FormatFloat(r, 'g', -1, 64), ok
} else if r, ok := v.(bool); ok {
return strconv.FormatBool(r), ok
} else if r, ok := v.(string); ok {
return r, ok
} else {
return "", false
}
} else {
return "", f
}
}
func (e PlaygroundExtras) GetDuration(name string) (time.Duration, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return time.Duration(r), ok
} else if r, ok := v.(float64); ok {
return time.Duration(r), ok
} else if r, ok := v.(string); ok {
if d, err := time.ParseDuration(r); err != nil {
return time.Duration(0), false
} else {
return d, true
}
} else {
return time.Duration(0), false
}
} else {
return time.Duration(0), f
}
}
type Playground struct {
Id string `json:"id" bson:"id"`
Domain string `json:"domain" bson:"domain"`
DefaultDinDInstanceImage string `json:"default_dind_instance_image" bson:"default_dind_instance_image"`
AvailableDinDInstanceImages []string `json:"available_dind_instance_images" bson:"available_dind_instance_images"`
AllowWindowsInstances bool `json:"allow_windows_instances" bson:"allow_windows_instances"`
DefaultSessionDuration time.Duration `json:"default_session_duration" bson:"default_session_duration"`
L2RouterIP string `json:"l2_router_ip" bson:"l2_router_ip"`
Extras PlaygroundExtras `json:"extras" bson:"extras"`
}

View File

@@ -0,0 +1,119 @@
package types
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/twinj/uuid"
)
func TestPlayground_Extras_GetInt(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"intFromInt": 10,
"intFromFloat": 32.0,
"intFromString": "15",
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetInt("intFromInt")
assert.True(t, found)
assert.Equal(t, 10, v)
v, found = p2.Extras.GetInt("intFromFloat")
assert.True(t, found)
assert.Equal(t, 32, v)
v, found = p2.Extras.GetInt("intFromString")
assert.True(t, found)
assert.Equal(t, 15, v)
}
func TestPlayground_Extras_GetString(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"stringFromInt": 10,
"stringFromFloat": 32.3,
"stringFromString": "15",
"stringFromBool": false,
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetString("stringFromInt")
assert.True(t, found)
assert.Equal(t, "10", v)
v, found = p2.Extras.GetString("stringFromFloat")
assert.True(t, found)
assert.Equal(t, "32.3", v)
v, found = p2.Extras.GetString("stringFromString")
assert.True(t, found)
assert.Equal(t, "15", v)
v, found = p2.Extras.GetString("stringFromBool")
assert.True(t, found)
assert.Equal(t, "false", v)
}
func TestPlayground_Extras_GetDuration(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"durationFromInt": 10,
"durationFromFloat": 32.3,
"durationFromString": "4h",
"durationFromDuration": time.Hour * 3,
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetDuration("durationFromInt")
assert.True(t, found)
assert.Equal(t, time.Duration(10), v)
v, found = p2.Extras.GetDuration("durationFromFloat")
assert.True(t, found)
assert.Equal(t, time.Duration(32), v)
v, found = p2.Extras.GetDuration("durationFromString")
assert.True(t, found)
assert.Equal(t, time.Hour*4, v)
v, found = p2.Extras.GetDuration("durationFromDuration")
assert.True(t, found)
assert.Equal(t, time.Hour*3, v)
}

View File

@@ -15,4 +15,5 @@ type Session struct {
ImageName string `json:"image_name" bson:"image_name"`
Host string `json:"host" bson:"host"`
UserId string `json:"user_id" bson:"user_id"`
PlaygroundId string `json:"playground_id" bson:"playground_id"`
}