This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-07-25 16:36:10 -03:00
parent 8424479e76
commit 53e6078cc5
22 changed files with 307 additions and 144 deletions

View File

@@ -2,7 +2,6 @@ package config
import ( import (
"flag" "flag"
"log"
"os" "os"
"regexp" "regexp"
"time" "time"
@@ -21,7 +20,7 @@ const (
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex) var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
var AliasFilter = regexp.MustCompile(AliasPortGroupRegex) var AliasFilter = regexp.MustCompile(AliasPortGroupRegex)
var SSLPortNumber, PortNumber, Key, Cert, SessionsFile, PWDContainerName, PWDCName, HashKey, SSHKeyPath string var SSLPortNumber, PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, PWDCName, HashKey, SSHKeyPath string
var MaxLoadAvg float64 var MaxLoadAvg float64
func ParseFlags() { func ParseFlags() {
@@ -31,13 +30,13 @@ func ParseFlags() {
flag.StringVar(&Cert, "cert", "./pwd/server.pem", "Give a SSL cert") flag.StringVar(&Cert, "cert", "./pwd/server.pem", "Give a SSL cert")
flag.StringVar(&SessionsFile, "save", "./pwd/sessions", "Tell where to store sessions file") flag.StringVar(&SessionsFile, "save", "./pwd/sessions", "Tell where to store sessions file")
flag.StringVar(&PWDContainerName, "name", "pwd", "Container name used to run PWD (used to be able to connect it to the networks it creates)") flag.StringVar(&PWDContainerName, "name", "pwd", "Container name used to run PWD (used to be able to connect it to the networks it creates)")
flag.StringVar(&PWDCName, "cname", "host1", "CNAME given to this host") flag.StringVar(&L2ContainerName, "l2", "l2", "Container name used to run L2 Router")
flag.StringVar(&L2Subdomain, "l2-subdomain", "direct", "Subdomain to the L2 Router")
flag.StringVar(&PWDCName, "cname", "", "CNAME given to this host")
flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies") flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies")
flag.Float64Var(&MaxLoadAvg, "maxload", 100, "Maximum allowed load average before failing ping requests") flag.Float64Var(&MaxLoadAvg, "maxload", 100, "Maximum allowed load average before failing ping requests")
flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use") flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use")
flag.Parse() flag.Parse()
log.Println("*****************************", SSHKeyPath)
} }
func GetDindImageName() string { func GetDindImageName() string {
dindImage := os.Getenv("DIND_IMAGE") dindImage := os.Getenv("DIND_IMAGE")

View File

@@ -1,21 +1,20 @@
version: '3.2' version: '3.2'
services: services:
haproxy: haproxy:
container_name: proxy container_name: haproxy
image: haproxy image: haproxy
ports: ports:
- "80:8080" - "80:8080"
- "443:8443"
volumes: volumes:
- ./haproxy:/usr/local/etc/haproxy - ./haproxy:/usr/local/etc/haproxy
pwd1:
pwd:
# pwd daemon container always needs to be named this way # pwd daemon container always needs to be named this way
container_name: pwd1 container_name: pwd
# use the latest golang image # use the latest golang image
image: golang image: golang
# go to the right place and starts the app # go to the right place and starts the app
command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker; go run api.go -save /pwd/sessions1 -name pwd1 -cname host1' command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker; go run api.go -save /pwd/sessions -name l2'
volumes: volumes:
# since this app creates networks and launches containers, we need to talk to docker daemon # since this app creates networks and launches containers, we need to talk to docker daemon
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
@@ -24,37 +23,20 @@ services:
- sessions:/pwd - sessions:/pwd
environment: environment:
GOOGLE_RECAPTCHA_DISABLED: "true" GOOGLE_RECAPTCHA_DISABLED: "true"
ports: l2:
- "1022:1022" container_name: l2
pwd2:
# pwd daemon container always needs to be named this way
container_name: pwd2
# use the latest golang image # use the latest golang image
image: golang image: golang
# go to the right place and starts the app # go to the right place and starts the app
command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker; go run api.go -save /pwd/sessions2 -name pwd2 -cname host2' command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker/router/l2; go run l2.go -ssh_key_path /etc/ssh/ssh_host_rsa_key -name l2 -save /pwd/networks'
volumes: volumes:
# since this app creates networks and launches containers, we need to talk to docker daemon
- /var/run/docker.sock:/var/run/docker.sock
# mount the box mounted shared folder to the container
- $GOPATH/src:/go/src - $GOPATH/src:/go/src
- sessions:/pwd - /var/run/docker.sock:/var/run/docker.sock
environment: - networks:/pwd
GOOGLE_RECAPTCHA_DISABLED: "true"
ports: ports:
- "1023:1022" - "8022:22"
prometheus: - "8053:53"
container_name: prometheus - "443:443"
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
container_name: grafana
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- grafana:/var/lib/grafana
volumes: volumes:
sessions: sessions:
grafana: networks:

View File

@@ -4,11 +4,10 @@ import (
"log" "log"
"os" "os"
"github.com/docker/docker/client"
"github.com/googollee/go-socket.io" "github.com/googollee/go-socket.io"
"github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/config"
"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/event"
"github.com/play-with-docker/play-with-docker/provider"
"github.com/play-with-docker/play-with-docker/pwd" "github.com/play-with-docker/play-with-docker/pwd"
"github.com/play-with-docker/play-with-docker/storage" "github.com/play-with-docker/play-with-docker/storage"
) )
@@ -18,23 +17,18 @@ var e event.EventApi
var ws *socketio.Server var ws *socketio.Server
func Bootstrap() { func Bootstrap() {
c, err := client.NewEnvClient() sp := provider.NewLocalSessionProvider()
if err != nil {
log.Fatal(err)
}
d := docker.NewDocker(c)
e = event.NewLocalBroker() e = event.NewLocalBroker()
t := pwd.NewScheduler(e, d) t := pwd.NewScheduler(e, sp)
s, err := storage.NewFileStorage(config.SessionsFile) s, err := storage.NewFileStorage(config.SessionsFile)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
log.Fatal("Error initializing StorageAPI: ", err) log.Fatal("Error initializing StorageAPI: ", err)
} }
core = pwd.NewPWD(d, t, e, s) core = pwd.NewPWD(sp, t, e, s)
} }

View File

@@ -49,7 +49,10 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
log.Println(err) log.Println(err)
//TODO: Return some error code //TODO: Return some error code
} else { } else {
hostname := fmt.Sprintf("%s.%s", config.PWDCName, req.Host) hostname := req.Host
if config.PWDCName != "" {
hostname = fmt.Sprintf("%s.%s", config.PWDCName, req.Host)
}
// If request is not a form, return sessionId in the body // If request is not a form, return sessionId in the body
if req.Header.Get("X-Requested-With") == "XMLHttpRequest" { if req.Header.Get("X-Requested-With") == "XMLHttpRequest" {
resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname} resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname}

View File

@@ -8,25 +8,13 @@ frontend http-in
bind *:8080 bind *:8080
acl host_localhost hdr(host) localhost acl host_localhost hdr(host) localhost
acl host_pwd1 hdr_reg(host) -i ^.*\.?host1\.localhost?:?.*$ acl host_direct_localhost hdr_reg(host) -i ^.*\.direct\.localhost?:?.*$
acl host_pwd2 hdr_reg(host) -i ^.*\.?host2\.localhost?:?.*$
use_backend all if host_localhost use_backend pwd if host_localhost
use_backend pwd1 if host_pwd1 use_backend l2 if host_direct_localhost
use_backend pwd2 if host_pwd2
backend all backend pwd
balance roundrobin server node1 pwd:3000
option httpchk GET /ping HTTP/1.0 backend l2
http-check expect rstatus 200 server node2 l2:443
default-server inter 3s fall 3 rise 2
server node1 pwd1:3000 check
server node2 pwd2:3000 check
backend pwd1
server node1 pwd1:3000
backend pwd2
server node2 pwd2:3000

View File

@@ -0,0 +1,36 @@
package provider
import (
"sync"
"github.com/docker/docker/client"
"github.com/play-with-docker/play-with-docker/docker"
)
type localSessionProvider struct {
rw sync.Mutex
docker docker.DockerApi
}
func (p *localSessionProvider) GetDocker(sessionId string) (docker.DockerApi, error) {
p.rw.Lock()
defer p.rw.Unlock()
if p.docker != nil {
return p.docker, nil
}
c, err := client.NewEnvClient()
if err != nil {
return nil, err
}
d := docker.NewDocker(c)
p.docker = d
return d, nil
}
func NewLocalSessionProvider() *localSessionProvider {
return &localSessionProvider{}
}

10
provider/provider.go Normal file
View File

@@ -0,0 +1,10 @@
package provider
import "github.com/play-with-docker/play-with-docker/docker"
type InstanceProvider interface {
}
type SessionProvider interface {
GetDocker(sessionId string) (docker.DockerApi, error)
}

View File

@@ -11,12 +11,13 @@ import (
) )
func TestClientNew(t *testing.T) { func TestClientNew(t *testing.T) {
docker := &mockDocker{} d := &mockDocker{}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
sp := &mockSessionProvider{docker: d}
p := NewPWD(docker, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
@@ -27,12 +28,13 @@ func TestClientNew(t *testing.T) {
assert.Contains(t, session.Clients, client) assert.Contains(t, session.Clients, client)
} }
func TestClientCount(t *testing.T) { func TestClientCount(t *testing.T) {
docker := &mockDocker{} d := &mockDocker{}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
sp := &mockSessionProvider{docker: d}
p := NewPWD(docker, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
@@ -45,9 +47,10 @@ func TestClientCount(t *testing.T) {
func TestClientResizeViewPort(t *testing.T) { func TestClientResizeViewPort(t *testing.T) {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
docker := &mockDocker{} d := &mockDocker{}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
sp := &mockSessionProvider{docker: d}
broadcastedSessionId := "" broadcastedSessionId := ""
broadcastedArgs := []interface{}{} broadcastedArgs := []interface{}{}
@@ -60,7 +63,7 @@ func TestClientResizeViewPort(t *testing.T) {
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(docker, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -7,7 +7,7 @@ import (
dockerTypes "github.com/docker/docker/api/types" dockerTypes "github.com/docker/docker/api/types"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/provider"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
) )
@@ -20,11 +20,12 @@ type collectStatsTask struct {
previousCPU uint64 previousCPU uint64
previousSystem uint64 previousSystem uint64
docker docker.DockerApi sessionProvider provider.SessionProvider
} }
func (c collectStatsTask) Run(i *types.Instance) error { func (c collectStatsTask) Run(i *types.Instance) error {
reader, err := c.docker.GetContainerStats(i.Name) docker, _ := c.sessionProvider.GetDocker(i.SessionId)
reader, err := docker.GetContainerStats(i.Name)
if err != nil { if err != nil {
log.Println("Error while trying to collect instance stats", err) log.Println("Error while trying to collect instance stats", err)
return err return err

View File

@@ -15,6 +15,7 @@ import (
"github.com/play-with-docker/play-with-docker/docker" "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/event"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/router"
"golang.org/x/text/encoding" "golang.org/x/text/encoding"
) )
@@ -46,7 +47,7 @@ type InstanceConfig struct {
func (p *pwd) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error { func (p *pwd) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error {
defer observeAction("InstanceResizeTerminal", time.Now()) defer observeAction("InstanceResizeTerminal", time.Now())
return p.docker.ContainerResize(instance.Name, rows, cols) return p.docker(instance.SessionId).ContainerResize(instance.Name, rows, cols)
} }
func (p *pwd) InstanceAttachTerminal(instance *types.Instance) error { func (p *pwd) InstanceAttachTerminal(instance *types.Instance) error {
@@ -54,7 +55,7 @@ func (p *pwd) InstanceAttachTerminal(instance *types.Instance) error {
if getInstanceTermConn(instance.SessionId, instance.Name) != nil { if getInstanceTermConn(instance.SessionId, instance.Name) != nil {
return nil return nil
} }
conn, err := p.docker.CreateAttachConnection(instance.Name) conn, err := p.docker(instance.SessionId).CreateAttachConnection(instance.Name)
if err != nil { if err != nil {
return err return err
@@ -84,7 +85,7 @@ func (p *pwd) InstanceUploadFromUrl(instance *types.Instance, fileName, dest str
return fmt.Errorf("Could not download file [%s]. Status code: %d\n", url, resp.StatusCode) return fmt.Errorf("Could not download file [%s]. Status code: %d\n", url, resp.StatusCode)
} }
copyErr := p.docker.CopyToContainer(instance.Name, dest, fileName, resp.Body) copyErr := p.docker(instance.SessionId).CopyToContainer(instance.Name, dest, fileName, resp.Body)
if copyErr != nil { if copyErr != nil {
return fmt.Errorf("Error while downloading file [%s]. Error: %s\n", url, copyErr) return fmt.Errorf("Error while downloading file [%s]. Error: %s\n", url, copyErr)
@@ -96,7 +97,7 @@ func (p *pwd) InstanceUploadFromUrl(instance *types.Instance, fileName, dest str
func (p *pwd) getInstanceCWD(instance *types.Instance) (string, error) { func (p *pwd) getInstanceCWD(instance *types.Instance) (string, error) {
b := bytes.NewBufferString("") b := bytes.NewBufferString("")
if c, err := p.docker.ExecAttach(instance.Name, []string{"bash", "-c", `pwdx $(</var/run/cwd)`}, b); c > 0 { if c, err := p.docker(instance.SessionId).ExecAttach(instance.Name, []string{"bash", "-c", `pwdx $(</var/run/cwd)`}, b); c > 0 {
log.Println(b.String()) log.Println(b.String())
return "", fmt.Errorf("Error %d trying to get CWD", c) return "", fmt.Errorf("Error %d trying to get CWD", c)
} else if err != nil { } else if err != nil {
@@ -122,7 +123,7 @@ func (p *pwd) InstanceUploadFromReader(instance *types.Instance, fileName, dest
} }
} }
copyErr := p.docker.CopyToContainer(instance.Name, finalDest, fileName, reader) copyErr := p.docker(instance.SessionId).CopyToContainer(instance.Name, finalDest, fileName, reader)
if copyErr != nil { if copyErr != nil {
return fmt.Errorf("Error while uploading file [%s]. Error: %s\n", fileName, copyErr) return fmt.Errorf("Error while uploading file [%s]. Error: %s\n", fileName, copyErr)
@@ -171,7 +172,7 @@ func (p *pwd) InstanceDelete(session *types.Session, instance *types.Instance) e
if conn != nil { if conn != nil {
conn.Close() conn.Close()
} }
err := p.docker.DeleteContainer(instance.Name) err := p.docker(session.Id).DeleteContainer(instance.Name)
if err != nil && !strings.Contains(err.Error(), "No such container") { if err != nil && !strings.Contains(err.Error(), "No such container") {
log.Println(err) log.Println(err)
return err return err
@@ -243,7 +244,7 @@ func (p *pwd) InstanceNew(session *types.Session, conf InstanceConfig) (*types.I
} }
} }
ip, err := p.docker.CreateContainer(opts) ip, err := p.docker(session.Id).CreateContainer(opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -261,6 +262,7 @@ func (p *pwd) InstanceNew(session *types.Session, conf InstanceConfig) (*types.I
instance.ServerKey = conf.ServerKey instance.ServerKey = conf.ServerKey
instance.CACert = conf.CACert instance.CACert = conf.CACert
instance.Session = session instance.Session = session
instance.Proxy = router.EncodeHost(session.Id, ip, router.HostOpts{})
// For now this condition holds through. In the future we might need a more complex logic. // For now this condition holds through. In the future we might need a more complex logic.
instance.IsDockerHost = opts.Privileged instance.IsDockerHost = opts.Privileged
@@ -304,7 +306,7 @@ func (p *pwd) InstanceAllowedImages() []string {
func (p *pwd) InstanceExec(instance *types.Instance, cmd []string) (int, error) { func (p *pwd) InstanceExec(instance *types.Instance, cmd []string) (int, error) {
defer observeAction("InstanceExec", time.Now()) defer observeAction("InstanceExec", time.Now())
return p.docker.Exec(instance.Name, cmd) return p.docker(instance.SessionId).Exec(instance.Name, cmd)
} }
func getInstanceTermConn(sessionId, instanceName string) net.Conn { func getInstanceTermConn(sessionId, instanceName string) net.Conn {

View File

@@ -12,6 +12,7 @@ import (
"github.com/play-with-docker/play-with-docker/docker" "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/event"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/router"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -28,12 +29,13 @@ func TestInstanceResizeTerminal(t *testing.T) {
return nil return nil
} }
sp := &mockSessionProvider{docker: docker}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(docker, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
err := p.InstanceResizeTerminal(&types.Instance{Name: "foobar"}, 24, 80) err := p.InstanceResizeTerminal(&types.Instance{Name: "foobar"}, 24, 80)
@@ -50,12 +52,13 @@ func TestInstanceNew(t *testing.T) {
containerOpts = opts containerOpts = opts
return "10.0.0.1", nil return "10.0.0.1", nil
} }
sp := &mockSessionProvider{docker: dock}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
@@ -74,6 +77,7 @@ func TestInstanceNew(t *testing.T) {
IsDockerHost: true, IsDockerHost: true,
SessionId: session.Id, SessionId: session.Id,
Session: session, Session: session,
Proxy: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}),
} }
assert.Equal(t, expectedInstance, *instance) assert.Equal(t, expectedInstance, *instance)
@@ -101,12 +105,13 @@ func TestInstanceNew_Concurrency(t *testing.T) {
i++ i++
return fmt.Sprintf("10.0.0.%d", i), nil return fmt.Sprintf("10.0.0.%d", i), nil
} }
sp := &mockSessionProvider{docker: dock}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
@@ -142,12 +147,13 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
containerOpts = opts containerOpts = opts
return "10.0.0.1", nil return "10.0.0.1", nil
} }
sp := &mockSessionProvider{docker: dock}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
@@ -166,6 +172,7 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
SessionId: session.Id, SessionId: session.Id,
IsDockerHost: false, IsDockerHost: false,
Session: session, Session: session,
Proxy: instance.Proxy,
} }
assert.Equal(t, expectedInstance, *instance) assert.Equal(t, expectedInstance, *instance)
@@ -191,12 +198,13 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
containerOpts = opts containerOpts = opts
return "10.0.0.1", nil return "10.0.0.1", nil
} }
sp := &mockSessionProvider{docker: dock}
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, err := p.SessionNew(time.Hour, "", "", "") session, err := p.SessionNew(time.Hour, "", "", "")
@@ -215,6 +223,7 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
IsDockerHost: false, IsDockerHost: false,
Session: session, Session: session,
SessionId: session.Id, SessionId: session.Id,
Proxy: instance.Proxy,
} }
assert.Equal(t, expectedInstance, *instance) assert.Equal(t, expectedInstance, *instance)
@@ -238,8 +247,9 @@ func TestInstanceAllowedImages(t *testing.T) {
tasks := &mockTasks{} tasks := &mockTasks{}
e := event.NewLocalBroker() e := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
sp := &mockSessionProvider{docker: dock}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
expectedImages := []string{config.GetDindImageName(), "franela/dind:overlay2-dev", "franela/ucp:2.4.1"} expectedImages := []string{config.GetDindImageName(), "franela/dind:overlay2-dev", "franela/ucp:2.4.1"}
@@ -264,8 +274,9 @@ func TestTermConnAssignment(t *testing.T) {
// return error connection to unlock the goroutine // return error connection to unlock the goroutine
return errConn{}, nil return errConn{}, nil
} }
sp := &mockSessionProvider{docker: dock}
p := NewPWD(dock, tasks, e, storage) p := NewPWD(sp, tasks, e, storage)
session, _ := p.SessionNew(time.Hour, "", "", "") session, _ := p.SessionNew(time.Hour, "", "", "")
mockInstance := &types.Instance{ mockInstance := &types.Instance{
Name: fmt.Sprintf("%s_redis-master", session.Id[:8]), Name: fmt.Sprintf("%s_redis-master", session.Id[:8]),

View File

@@ -6,6 +6,7 @@ import (
"github.com/play-with-docker/play-with-docker/docker" "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/event"
"github.com/play-with-docker/play-with-docker/provider"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/storage" "github.com/play-with-docker/play-with-docker/storage"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@@ -44,11 +45,11 @@ func init() {
} }
type pwd struct { type pwd struct {
docker docker.DockerApi sessionProvider provider.SessionProvider
tasks SchedulerApi tasks SchedulerApi
event event.EventApi event event.EventApi
storage storage.StorageApi storage storage.StorageApi
clientCount int32 clientCount int32
} }
type PWDApi interface { type PWDApi interface {
@@ -80,8 +81,16 @@ type PWDApi interface {
ClientCount() int ClientCount() int
} }
func NewPWD(d docker.DockerApi, t SchedulerApi, e event.EventApi, s storage.StorageApi) *pwd { func NewPWD(sp provider.SessionProvider, t SchedulerApi, e event.EventApi, s storage.StorageApi) *pwd {
return &pwd{docker: d, tasks: t, event: e, storage: s} return &pwd{sessionProvider: sp, tasks: t, event: e, storage: s}
}
func (p *pwd) docker(sessionId string) docker.DockerApi {
d, err := p.sessionProvider.GetDocker(sessionId)
if err != nil {
panic("Should not have got here. Session always need to be validated before calling this.")
}
return d
} }
func (p *pwd) setGauges() { func (p *pwd) setGauges() {

View File

@@ -62,7 +62,7 @@ func (p *pwd) SessionNew(duration time.Duration, stack, stackName, imageName str
log.Printf("NewSession id=[%s]\n", s.Id) log.Printf("NewSession id=[%s]\n", s.Id)
if err := p.docker.CreateNetwork(s.Id); err != nil { if err := p.docker(s.Id).CreateNetwork(s.Id); err != nil {
log.Println("ERROR NETWORKING") log.Println("ERROR NETWORKING")
return nil, err return nil, err
} }
@@ -101,14 +101,14 @@ func (p *pwd) SessionClose(s *types.Session) error {
} }
} }
// Disconnect PWD daemon from the network // Disconnect PWD daemon from the network
if err := p.docker.DisconnectNetwork(config.PWDContainerName, s.Id); err != nil { if err := p.docker(s.Id).DisconnectNetwork(config.PWDContainerName, s.Id); err != nil {
if !strings.Contains(err.Error(), "is not connected to the network") { if !strings.Contains(err.Error(), "is not connected to the network") {
log.Println("ERROR NETWORKING") log.Println("ERROR NETWORKING")
return err return err
} }
} }
log.Printf("Disconnected pwd from network [%s]\n", s.Id) log.Printf("Disconnected pwd from network [%s]\n", s.Id)
if err := p.docker.DeleteNetwork(s.Id); err != nil { if err := p.docker(s.Id).DeleteNetwork(s.Id); err != nil {
if !strings.Contains(err.Error(), "not found") { if !strings.Contains(err.Error(), "not found") {
log.Println(err) log.Println(err)
return err return err
@@ -168,7 +168,7 @@ func (p *pwd) SessionDeployStack(s *types.Session) error {
cmd := fmt.Sprintf("docker swarm init --advertise-addr eth0 && docker-compose -f %s pull && docker stack deploy -c %s %s", file, file, s.StackName) cmd := fmt.Sprintf("docker swarm init --advertise-addr eth0 && docker-compose -f %s pull && docker stack deploy -c %s %s", file, file, s.StackName)
w := sessionBuilderWriter{sessionId: s.Id, event: p.event} w := sessionBuilderWriter{sessionId: s.Id, event: p.event}
code, err := p.docker.ExecAttach(i.Name, []string{"sh", "-c", cmd}, &w) code, err := p.docker(s.Id).ExecAttach(i.Name, []string{"sh", "-c", cmd}, &w)
if err != nil { if err != nil {
log.Printf("Error executing stack [%s]: %s\n", s.Stack, err) log.Printf("Error executing stack [%s]: %s\n", s.Stack, err)
return err return err
@@ -218,7 +218,7 @@ func (p *pwd) SessionSetup(session *types.Session, conf SessionSetupConf) error
return err return err
} }
if i.Docker == nil { if i.Docker == nil {
dock, err := p.docker.New(i.IP, i.Cert, i.Key) dock, err := p.docker(session.Id).New(i.IP, i.Cert, i.Key)
if err != nil { if err != nil {
return err return err
} }
@@ -254,7 +254,7 @@ func (p *pwd) SessionSetup(session *types.Session, conf SessionSetupConf) error
if c.IsSwarmManager || c.IsSwarmWorker { if c.IsSwarmManager || c.IsSwarmWorker {
// check if we have connection to the daemon, if not, create it // check if we have connection to the daemon, if not, create it
if i.Docker == nil { if i.Docker == nil {
dock, err := p.docker.New(i.IP, i.Cert, i.Key) dock, err := p.docker(session.Id).New(i.IP, i.Cert, i.Key)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
@@ -334,7 +334,7 @@ func (p *pwd) scheduleSessionClose(s *types.Session) {
} }
func (p *pwd) connectToNetwork(s *types.Session) error { func (p *pwd) connectToNetwork(s *types.Session) error {
ip, err := p.docker.ConnectNetwork(config.PWDContainerName, s.Id, s.PwdIpAddress) ip, err := p.docker(s.Id).ConnectNetwork(config.PWDContainerName, s.Id, s.PwdIpAddress)
if err != nil { if err != nil {
log.Println("ERROR NETWORKING") log.Println("ERROR NETWORKING")
return err return err

View File

@@ -0,0 +1,11 @@
package pwd
import "github.com/play-with-docker/play-with-docker/docker"
type mockSessionProvider struct {
docker docker.DockerApi
}
func (p *mockSessionProvider) GetDocker(sessionId string) (docker.DockerApi, error) {
return p.docker, nil
}

View File

@@ -30,6 +30,7 @@ func TestSessionNew(t *testing.T) {
connectIP = ip connectIP = ip
return "10.0.0.1", nil return "10.0.0.1", nil
} }
sp := &mockSessionProvider{docker: docker}
var scheduledSession *types.Session var scheduledSession *types.Session
tasks := &mockTasks{} tasks := &mockTasks{}
@@ -44,7 +45,7 @@ func TestSessionNew(t *testing.T) {
return nil return nil
} }
p := NewPWD(docker, tasks, ev, storage) p := NewPWD(sp, tasks, ev, storage)
before := time.Now() before := time.Now()
@@ -166,11 +167,12 @@ func TestSessionSetup(t *testing.T) {
assert.Fail(t, "Shouldn't have reached here.") assert.Fail(t, "Shouldn't have reached here.")
return nil, nil return nil, nil
} }
sp := &mockSessionProvider{docker: dock}
tasks := &mockTasks{} tasks := &mockTasks{}
ev := event.NewLocalBroker() ev := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
p := NewPWD(dock, tasks, ev, storage) p := NewPWD(sp, tasks, ev, storage)
s, e := p.SessionNew(time.Hour, "", "", "") s, e := p.SessionNew(time.Hour, "", "", "")
assert.Nil(t, e) assert.Nil(t, e)
@@ -215,6 +217,7 @@ func TestSessionSetup(t *testing.T) {
IsDockerHost: true, IsDockerHost: true,
Session: s, Session: s,
Docker: manager1Received.Docker, Docker: manager1Received.Docker,
Proxy: manager1Received.Proxy,
}, manager1Received) }, manager1Received)
manager2 := fmt.Sprintf("%s_manager2", s.Id[:8]) manager2 := fmt.Sprintf("%s_manager2", s.Id[:8])
@@ -229,6 +232,7 @@ func TestSessionSetup(t *testing.T) {
SessionId: s.Id, SessionId: s.Id,
Session: s, Session: s,
Docker: manager2Received.Docker, Docker: manager2Received.Docker,
Proxy: manager2Received.Proxy,
}, manager2Received) }, manager2Received)
manager3 := fmt.Sprintf("%s_manager3", s.Id[:8]) manager3 := fmt.Sprintf("%s_manager3", s.Id[:8])
@@ -243,6 +247,7 @@ func TestSessionSetup(t *testing.T) {
IsDockerHost: true, IsDockerHost: true,
Session: s, Session: s,
Docker: manager3Received.Docker, Docker: manager3Received.Docker,
Proxy: manager3Received.Proxy,
}, manager3Received) }, manager3Received)
worker1 := fmt.Sprintf("%s_worker1", s.Id[:8]) worker1 := fmt.Sprintf("%s_worker1", s.Id[:8])
@@ -257,6 +262,7 @@ func TestSessionSetup(t *testing.T) {
IsDockerHost: true, IsDockerHost: true,
Session: s, Session: s,
Docker: worker1Received.Docker, Docker: worker1Received.Docker,
Proxy: worker1Received.Proxy,
}, worker1Received) }, worker1Received)
other := fmt.Sprintf("%s_other", s.Id[:8]) other := fmt.Sprintf("%s_other", s.Id[:8])
@@ -271,6 +277,7 @@ func TestSessionSetup(t *testing.T) {
IsDockerHost: true, IsDockerHost: true,
Session: s, Session: s,
Docker: otherReceived.Docker, Docker: otherReceived.Docker,
Proxy: otherReceived.Proxy,
}, otherReceived) }, otherReceived)
assert.True(t, swarmInitOnMaster1) assert.True(t, swarmInitOnMaster1)
@@ -284,8 +291,9 @@ func TestSessionPrepareOnce(t *testing.T) {
tasks := &mockTasks{} tasks := &mockTasks{}
ev := event.NewLocalBroker() ev := event.NewLocalBroker()
storage := &mockStorage{} storage := &mockStorage{}
sp := &mockSessionProvider{docker: dock}
p := NewPWD(dock, tasks, ev, storage) p := NewPWD(sp, tasks, ev, storage)
session := &types.Session{Id: "1234"} session := &types.Session{Id: "1234"}
prepared, err := p.prepareSession(session) prepared, err := p.prepareSession(session)
assert.True(t, preparedSessions[session.Id]) assert.True(t, preparedSessions[session.Id])

View File

@@ -16,6 +16,7 @@ import (
"github.com/docker/go-connections/tlsconfig" "github.com/docker/go-connections/tlsconfig"
"github.com/play-with-docker/play-with-docker/docker" "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/event"
"github.com/play-with-docker/play-with-docker/provider"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
) )
@@ -112,8 +113,8 @@ func (sch *scheduler) Schedule(s *types.Session) {
func (sch *scheduler) Unschedule(s *types.Session) { func (sch *scheduler) Unschedule(s *types.Session) {
} }
func NewScheduler(e event.EventApi, d docker.DockerApi) *scheduler { func NewScheduler(e event.EventApi, sp provider.SessionProvider) *scheduler {
s := &scheduler{event: e} s := &scheduler{event: e}
s.periodicTasks = []periodicTask{&collectStatsTask{docker: d}, &checkSwarmStatusTask{}, &checkUsedPortsTask{}, &checkSwarmUsedPortsTask{}} s.periodicTasks = []periodicTask{&collectStatsTask{sessionProvider: sp}, &checkSwarmStatusTask{}, &checkUsedPortsTask{}, &checkSwarmUsedPortsTask{}}
return s return s
} }

View File

@@ -30,6 +30,7 @@ type Instance struct {
IsDockerHost bool `json:"is_docker_host" bson:"is_docker_host"` IsDockerHost bool `json:"is_docker_host" bson:"is_docker_host"`
SessionId string `json:"session_id" bson:"session_id"` SessionId string `json:"session_id" bson:"session_id"`
SessionPrefix string `json:"session_prefix" bson:"session_prefix"` SessionPrefix string `json:"session_prefix" bson:"session_prefix"`
Proxy string `json:"proxy" bson:"proxy"`
Docker docker.DockerApi `json:"-"` Docker docker.DockerApi `json:"-"`
Session *Session `json:"-" bson:"-"` Session *Session `json:"-" bson:"-"`
ctx context.Context `json:"-" bson:"-"` ctx context.Context `json:"-" bson:"-"`

71
router/host.go Normal file
View File

@@ -0,0 +1,71 @@
package router
import (
"fmt"
"regexp"
"strconv"
"strings"
)
const hostPattern = "^.*ip([0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3})-([0-9|a-z]+)(?:-?([0-9]{1,5}))?(?:\\.([a-z|A-Z|0-9|_|\\-\\.]+))?(?:\\:([0-9]{1,5}))?$"
var hostRegex *regexp.Regexp
func init() {
hostRegex = regexp.MustCompile(hostPattern)
}
type HostOpts struct {
TLD string
EncodedPort int
Port int
}
type HostInfo struct {
SessionId string
InstanceIP string
TLD string
EncodedPort int
Port int
}
func EncodeHost(sessionId, instanceIP string, opts HostOpts) string {
encodedIP := strings.Replace(instanceIP, ".", "-", -1)
sub := fmt.Sprintf("ip%s-%s", encodedIP, sessionId)
if opts.EncodedPort > 0 {
sub = fmt.Sprintf("%s-%d", sub, opts.EncodedPort)
}
if opts.TLD != "" {
sub = fmt.Sprintf("%s.%s", sub, opts.TLD)
}
if opts.Port > 0 {
sub = fmt.Sprintf("%s:%d", sub, opts.Port)
}
return sub
}
func DecodeHost(host string) (HostInfo, error) {
info := HostInfo{}
matches := hostRegex.FindStringSubmatch(host)
if len(matches) != 6 {
return HostInfo{}, fmt.Errorf("Couldn't find host in string")
}
info.InstanceIP = strings.Replace(matches[1], "-", ".", -1)
info.SessionId = matches[2]
info.TLD = matches[4]
if matches[3] != "" {
i, _ := strconv.Atoi(matches[3])
info.EncodedPort = i
}
if matches[5] != "" {
i, _ := strconv.Atoi(matches[5])
info.Port = i
}
return info, nil
}

45
router/host_test.go Normal file
View File

@@ -0,0 +1,45 @@
package router
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEncodeHostInfo(t *testing.T) {
host := EncodeHost("aaabbbcccddd", "10.0.0.1", HostOpts{})
assert.Equal(t, "ip10-0-0-1-aaabbbcccddd", host)
opts := HostOpts{EncodedPort: 8080}
host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts)
assert.Equal(t, "ip10-0-0-1-aaabbbcccddd-8080", host)
opts = HostOpts{TLD: "foo.bar"}
host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts)
assert.Equal(t, "ip10-0-0-1-aaabbbcccddd.foo.bar", host)
opts = HostOpts{TLD: "foo.bar", EncodedPort: 8080, Port: 443}
host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts)
assert.Equal(t, "ip10-0-0-1-aaabbbcccddd-8080.foo.bar:443", host)
}
func TestDecodeHostInfo(t *testing.T) {
info, err := DecodeHost("ip10-0-0-1-aaabbbcccddd")
assert.Nil(t, err)
assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd"}, info)
info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080")
assert.Nil(t, err)
assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080}, info)
info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080.foo.bar")
assert.Nil(t, err)
assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080, TLD: "foo.bar"}, info)
info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080.foo.bar:443")
assert.Nil(t, err)
assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080, TLD: "foo.bar", Port: 443}, info)
_, err = DecodeHost("ip10-0-0-1")
assert.NotNil(t, err)
}

View File

@@ -7,7 +7,6 @@ import (
"log" "log"
"net" "net"
"os" "os"
"strings"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@@ -18,31 +17,23 @@ import (
) )
func director(host string) (*net.TCPAddr, error) { func director(host string) (*net.TCPAddr, error) {
chunks := strings.Split(host, ":") info, err := router.DecodeHost(host)
matches := config.NameFilter.FindStringSubmatch(chunks[0]) if err != nil {
return nil, err
var rawHost, port string
if len(matches) == 3 {
rawHost = matches[1]
port = matches[2]
} else if len(matches) == 2 {
rawHost = matches[1]
} else {
return nil, fmt.Errorf("Couldn't find host in string")
} }
if port == "" { port := info.Port
if len(chunks) == 2 {
port = chunks[1] if info.EncodedPort > 0 {
} else { port = info.EncodedPort
port = "80"
}
} }
dstHost := strings.Replace(rawHost, "-", ".", -1) if port == 0 {
// TODO: Should default depending on the protocol
port = 80
}
t, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%s", dstHost, port)) t, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", info.InstanceIP, port))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -7,41 +7,38 @@ import (
) )
func TestDirector(t *testing.T) { func TestDirector(t *testing.T) {
addr, err := director("ip10-0-0-1-8080.foo.bar") addr, err := director("ip10-0-0-1-aabb-8080.foo.bar")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:8080", addr.String()) assert.Equal(t, "10.0.0.1:8080", addr.String())
addr, err = director("ip10-0-0-1.foo.bar") addr, err = director("ip10-0-0-1-aabb.foo.bar")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:80", addr.String()) assert.Equal(t, "10.0.0.1:80", addr.String())
addr, err = director("ip10-0-0-1.foo.bar:9090") addr, err = director("ip10-0-0-1-aabb.foo.bar:9090")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:9090", addr.String()) assert.Equal(t, "10.0.0.1:9090", addr.String())
addr, err = director("ip10-0-0-1-2222.foo.bar:9090") addr, err = director("ip10-0-0-1-aabb-2222.foo.bar:9090")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:2222", addr.String()) assert.Equal(t, "10.0.0.1:2222", addr.String())
addr, err = director("lala.ip10-0-0-1-2222.foo.bar") addr, err = director("lala.ip10-0-0-1-aabb-2222.foo.bar")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:2222", addr.String()) assert.Equal(t, "10.0.0.1:2222", addr.String())
addr, err = director("lala.ip10-0-0-1-2222") addr, err = director("lala.ip10-0-0-1-aabb-2222")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:2222", addr.String()) assert.Equal(t, "10.0.0.1:2222", addr.String())
addr, err = director("ip10-0-0-1-2222") addr, err = director("ip10-0-0-1-aabb-2222")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:2222", addr.String()) assert.Equal(t, "10.0.0.1:2222", addr.String())
addr, err = director("ip10-0-0-1") addr, err = director("ip10-0-0-1-aabb")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "10.0.0.1:80", addr.String()) assert.Equal(t, "10.0.0.1:80", addr.String())
_, err = director("lala10-0-0-1.foo.bar") _, err = director("lala10-0-0-1-aabb.foo.bar")
assert.NotNil(t, err)
_, err = director("ip10-0-0-1-10-20")
assert.NotNil(t, err) assert.NotNil(t, err)
} }

View File

@@ -241,7 +241,7 @@
} }
$scope.getProxyUrl = function(instance, port) { $scope.getProxyUrl = function(instance, port) {
var url = window.location.protocol + '//pwd' + instance.ip.replace(/\./g, '-') + '-' + port + '.' + window.location.host; var url = window.location.protocol + '//' + instance.proxy + '-' + port + '.' + window.location.host;
return url; return url;
} }