Merge pull request #13 from xetorthio/session_setup
Add session templates
This commit is contained in:
1
api.go
1
api.go
@@ -61,6 +61,7 @@ func main() {
|
|||||||
r.HandleFunc("/ping", handlers.Ping).Methods("GET")
|
r.HandleFunc("/ping", handlers.Ping).Methods("GET")
|
||||||
corsRouter.HandleFunc("/instances/images", handlers.GetInstanceImages).Methods("GET")
|
corsRouter.HandleFunc("/instances/images", handlers.GetInstanceImages).Methods("GET")
|
||||||
corsRouter.HandleFunc("/sessions/{sessionId}", handlers.GetSession).Methods("GET")
|
corsRouter.HandleFunc("/sessions/{sessionId}", handlers.GetSession).Methods("GET")
|
||||||
|
corsRouter.HandleFunc("/sessions/{sessionId}/setup", handlers.SessionSetup).Methods("POST")
|
||||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances", handlers.NewInstance).Methods("POST")
|
corsRouter.HandleFunc("/sessions/{sessionId}/instances", handlers.NewInstance).Methods("POST")
|
||||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/uploads", handlers.FileUpload).Methods("POST")
|
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/uploads", handlers.FileUpload).Methods("POST")
|
||||||
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", handlers.DeleteInstance).Methods("DELETE")
|
corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", handlers.DeleteInstance).Methods("DELETE")
|
||||||
|
|||||||
@@ -4,22 +4,27 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -44,6 +49,14 @@ type DockerApi interface {
|
|||||||
DisconnectNetwork(containerId, networkId string) error
|
DisconnectNetwork(containerId, networkId string) error
|
||||||
DeleteNetwork(id string) error
|
DeleteNetwork(id string) error
|
||||||
Exec(instanceName string, command []string) (int, error)
|
Exec(instanceName string, command []string) (int, error)
|
||||||
|
New(ip string, cert, key []byte) (DockerApi, error)
|
||||||
|
SwarmInit() (*SwarmTokens, error)
|
||||||
|
SwarmJoin(addr, token string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwarmTokens struct {
|
||||||
|
Manager string
|
||||||
|
Worker string
|
||||||
}
|
}
|
||||||
|
|
||||||
type docker struct {
|
type docker struct {
|
||||||
@@ -396,6 +409,73 @@ func (d *docker) DeleteNetwork(id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *docker) New(ip string, cert, key []byte) (DockerApi, error) {
|
||||||
|
// We check if the client needs to use TLS
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if len(cert) > 0 && len(key) > 0 {
|
||||||
|
tlsConfig = tlsconfig.ClientDefault()
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
tlsCert, err := tls.X509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 1 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext}
|
||||||
|
if tlsConfig != nil {
|
||||||
|
transport.TLSClientConfig = tlsConfig
|
||||||
|
}
|
||||||
|
cli := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
c, err := client.NewClient(fmt.Sprintf("http://%s:2375", ip), api.DefaultVersion, cli, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not connect to DinD docker daemon. %s", err)
|
||||||
|
}
|
||||||
|
// try to connect up to 5 times and then give up
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
_, err := c.Ping(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
if client.IsErrConnectionFailed(err) {
|
||||||
|
// connection has failed, maybe instance is not ready yet, sleep and retry
|
||||||
|
log.Printf("Connection to [%s] has failed, maybe instance is not ready yet, sleeping and retrying in 1 second. Try #%d\n", fmt.Sprintf("http://%s:2375", ip), i+1)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NewDocker(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *docker) SwarmInit() (*SwarmTokens, error) {
|
||||||
|
req := swarm.InitRequest{AdvertiseAddr: "eth0", ListenAddr: "0.0.0.0:2377"}
|
||||||
|
_, err := d.c.SwarmInit(context.Background(), req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swarmInfo, err := d.c.SwarmInspect(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SwarmTokens{
|
||||||
|
Worker: swarmInfo.JoinTokens.Worker,
|
||||||
|
Manager: swarmInfo.JoinTokens.Manager,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (d *docker) SwarmJoin(addr, token string) error {
|
||||||
|
req := swarm.JoinRequest{RemoteAddrs: []string{addr}, JoinToken: token, ListenAddr: "0.0.0.0:2377", AdvertiseAddr: "eth0"}
|
||||||
|
return d.c.SwarmJoin(context.Background(), req)
|
||||||
|
}
|
||||||
|
|
||||||
func NewDocker(c *client.Client) *docker {
|
func NewDocker(c *client.Client) *docker {
|
||||||
return &docker{c: c}
|
return &docker{c: c}
|
||||||
}
|
}
|
||||||
|
|||||||
35
handlers/session_setup.go
Normal file
35
handlers/session_setup.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/play-with-docker/play-with-docker/pwd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SessionSetup(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
sessionId := vars["sessionId"]
|
||||||
|
|
||||||
|
body := pwd.SessionSetupConf{}
|
||||||
|
|
||||||
|
json.NewDecoder(req.Body).Decode(&body)
|
||||||
|
|
||||||
|
s := core.SessionGet(sessionId)
|
||||||
|
|
||||||
|
if len(s.Instances) > 0 {
|
||||||
|
log.Println("Cannot setup a session that contains instances")
|
||||||
|
rw.WriteHeader(http.StatusConflict)
|
||||||
|
rw.Write([]byte("Cannot setup a session that contains instances"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := core.SessionSetup(s, body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ type mockDocker struct {
|
|||||||
connectNetwork func(container, network, ip string) (string, error)
|
connectNetwork func(container, network, ip string) (string, error)
|
||||||
containerResize func(string, uint, uint) error
|
containerResize func(string, uint, uint) error
|
||||||
createContainer func(opts docker.CreateContainerOpts) (string, error)
|
createContainer func(opts docker.CreateContainerOpts) (string, error)
|
||||||
|
execAttach func(instanceName string, command []string, out io.Writer) (int, error)
|
||||||
|
new func(ip string, cert, key []byte) (docker.DockerApi, error)
|
||||||
|
swarmInit func() (*docker.SwarmTokens, error)
|
||||||
|
swarmJoin func(addr, token string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockDocker) CreateNetwork(id string) error {
|
func (m *mockDocker) CreateNetwork(id string) error {
|
||||||
@@ -64,6 +68,9 @@ func (m *mockDocker) CreateContainer(opts docker.CreateContainerOpts) (string, e
|
|||||||
return "10.0.0.1", nil
|
return "10.0.0.1", nil
|
||||||
}
|
}
|
||||||
func (m *mockDocker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
|
func (m *mockDocker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
|
||||||
|
if m.execAttach != nil {
|
||||||
|
return m.execAttach(instanceName, command, out)
|
||||||
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
func (m *mockDocker) DisconnectNetwork(containerId, networkId string) error {
|
func (m *mockDocker) DisconnectNetwork(containerId, networkId string) error {
|
||||||
@@ -75,6 +82,24 @@ func (m *mockDocker) DeleteNetwork(id string) error {
|
|||||||
func (m *mockDocker) Exec(instanceName string, command []string) (int, error) {
|
func (m *mockDocker) Exec(instanceName string, command []string) (int, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
func (m *mockDocker) New(ip string, cert, key []byte) (docker.DockerApi, error) {
|
||||||
|
if m.new != nil {
|
||||||
|
return m.new(ip, cert, key)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockDocker) SwarmInit() (*docker.SwarmTokens, error) {
|
||||||
|
if m.swarmInit != nil {
|
||||||
|
return m.swarmInit()
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockDocker) SwarmJoin(addr, token string) error {
|
||||||
|
if m.swarmJoin != nil {
|
||||||
|
return m.swarmJoin(addr, token)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type mockConn struct {
|
type mockConn struct {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type PWDApi interface {
|
|||||||
SessionDeployStack(session *Session) error
|
SessionDeployStack(session *Session) error
|
||||||
SessionGet(id string) *Session
|
SessionGet(id string) *Session
|
||||||
SessionLoadAndPrepare() error
|
SessionLoadAndPrepare() error
|
||||||
|
SessionSetup(session *Session, conf SessionSetupConf) error
|
||||||
|
|
||||||
InstanceNew(session *Session, conf InstanceConfig) (*Instance, error)
|
InstanceNew(session *Session, conf InstanceConfig) (*Instance, error)
|
||||||
InstanceResizeTerminal(instance *Instance, cols, rows uint) error
|
InstanceResizeTerminal(instance *Instance, cols, rows uint) error
|
||||||
|
|||||||
100
pwd/session.go
100
pwd/session.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/twinj/uuid"
|
"github.com/twinj/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,6 +24,17 @@ func (s *sessionBuilderWriter) Write(p []byte) (n int, err error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionSetupConf struct {
|
||||||
|
Instances []SessionSetupInstanceConf `json:"instances"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionSetupInstanceConf struct {
|
||||||
|
Image string `json:"image"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
IsSwarmManager bool `json:"is_swarm_manager"`
|
||||||
|
IsSwarmWorker bool `json:"is_swarm_worker"`
|
||||||
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
rw sync.Mutex
|
rw sync.Mutex
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
@@ -209,6 +221,94 @@ func (p *pwd) SessionLoadAndPrepare() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pwd) SessionSetup(session *Session, conf SessionSetupConf) error {
|
||||||
|
var tokens *docker.SwarmTokens = nil
|
||||||
|
var firstSwarmManager *Instance = nil
|
||||||
|
|
||||||
|
// first look for a swarm manager and create it
|
||||||
|
for _, conf := range conf.Instances {
|
||||||
|
if conf.IsSwarmManager {
|
||||||
|
instanceConf := InstanceConfig{
|
||||||
|
ImageName: conf.Image,
|
||||||
|
Hostname: conf.Hostname,
|
||||||
|
}
|
||||||
|
i, err := p.InstanceNew(session, instanceConf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i.docker == nil {
|
||||||
|
dock, err := p.docker.New(i.IP, i.Cert, i.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.docker = dock
|
||||||
|
}
|
||||||
|
tkns, err := i.docker.SwarmInit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tokens = tkns
|
||||||
|
firstSwarmManager = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now create the rest in parallel
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for _, c := range conf.Instances {
|
||||||
|
if firstSwarmManager != nil && c.Hostname != firstSwarmManager.Hostname {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(c SessionSetupInstanceConf) {
|
||||||
|
defer wg.Done()
|
||||||
|
instanceConf := InstanceConfig{
|
||||||
|
ImageName: c.Image,
|
||||||
|
Hostname: c.Hostname,
|
||||||
|
}
|
||||||
|
i, err := p.InstanceNew(session, instanceConf)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.IsSwarmManager || c.IsSwarmWorker {
|
||||||
|
// check if we have connection to the daemon, if not, create it
|
||||||
|
if i.docker == nil {
|
||||||
|
dock, err := p.docker.New(i.IP, i.Cert, i.Key)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.docker = dock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstSwarmManager != nil {
|
||||||
|
if c.IsSwarmManager {
|
||||||
|
// this is a swarm manager
|
||||||
|
// cluster has already been initiated, join as manager
|
||||||
|
err := i.docker.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Manager)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.IsSwarmWorker {
|
||||||
|
// this is a swarm worker
|
||||||
|
err := i.docker.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Worker)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// This function should be called any time a session needs to be prepared:
|
// This function should be called any time a session needs to be prepared:
|
||||||
// 1. Like when it is created
|
// 1. Like when it is created
|
||||||
// 2. When it was loaded from storage
|
// 2. When it was loaded from storage
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package pwd
|
package pwd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,3 +82,199 @@ func TestSessionNew(t *testing.T) {
|
|||||||
assert.Equal(t, expectedSessions, sessions)
|
assert.Equal(t, expectedSessions, sessions)
|
||||||
assert.True(t, saveCalled)
|
assert.True(t, saveCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionSetup(t *testing.T) {
|
||||||
|
swarmInitOnMaster1 := false
|
||||||
|
manager2JoinedHasManager := false
|
||||||
|
manager3JoinedHasManager := false
|
||||||
|
worker1JoinedHasWorker := false
|
||||||
|
|
||||||
|
dock := &mockDocker{}
|
||||||
|
dock.createContainer = func(opts docker.CreateContainerOpts) (string, error) {
|
||||||
|
if opts.Hostname == "manager1" {
|
||||||
|
return "10.0.0.1", nil
|
||||||
|
} else if opts.Hostname == "manager2" {
|
||||||
|
return "10.0.0.2", nil
|
||||||
|
} else if opts.Hostname == "manager3" {
|
||||||
|
return "10.0.0.3", nil
|
||||||
|
} else if opts.Hostname == "worker1" {
|
||||||
|
return "10.0.0.4", nil
|
||||||
|
} else if opts.Hostname == "other" {
|
||||||
|
return "10.0.0.5", nil
|
||||||
|
} else {
|
||||||
|
assert.Fail(t, "Should not have reached here")
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
dock.new = func(ip string, cert, key []byte) (docker.DockerApi, error) {
|
||||||
|
if ip == "10.0.0.1" {
|
||||||
|
return &mockDocker{
|
||||||
|
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||||
|
swarmInitOnMaster1 = true
|
||||||
|
return &docker.SwarmTokens{Worker: "worker-join-token", Manager: "manager-join-token"}, nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if ip == "10.0.0.2" {
|
||||||
|
return &mockDocker{
|
||||||
|
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
swarmJoin: func(addr, token string) error {
|
||||||
|
if addr == "10.0.0.1:2377" && token == "manager-join-token" {
|
||||||
|
manager2JoinedHasManager = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if ip == "10.0.0.3" {
|
||||||
|
return &mockDocker{
|
||||||
|
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
swarmJoin: func(addr, token string) error {
|
||||||
|
if addr == "10.0.0.1:2377" && token == "manager-join-token" {
|
||||||
|
manager3JoinedHasManager = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if ip == "10.0.0.4" {
|
||||||
|
return &mockDocker{
|
||||||
|
swarmInit: func() (*docker.SwarmTokens, error) {
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
swarmJoin: func(addr, token string) error {
|
||||||
|
if addr == "10.0.0.1:2377" && token == "worker-join-token" {
|
||||||
|
worker1JoinedHasWorker = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
assert.Fail(t, "Shouldn't have reached here.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
tasks := &mockTasks{}
|
||||||
|
broadcast := &mockBroadcast{}
|
||||||
|
storage := &mockStorage{}
|
||||||
|
|
||||||
|
p := NewPWD(dock, tasks, broadcast, storage)
|
||||||
|
s, e := p.SessionNew(time.Hour, "", "")
|
||||||
|
assert.Nil(t, e)
|
||||||
|
|
||||||
|
err := p.SessionSetup(s, SessionSetupConf{
|
||||||
|
Instances: []SessionSetupInstanceConf{
|
||||||
|
{
|
||||||
|
Image: "franela/dind",
|
||||||
|
IsSwarmManager: true,
|
||||||
|
Hostname: "manager1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IsSwarmManager: true,
|
||||||
|
Hostname: "manager2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Image: "franela/dind:overlay2-dev",
|
||||||
|
IsSwarmManager: true,
|
||||||
|
Hostname: "manager3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IsSwarmWorker: true,
|
||||||
|
Hostname: "worker1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Hostname: "other",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 5, len(s.Instances))
|
||||||
|
|
||||||
|
manager1 := fmt.Sprintf("%s_manager1", s.Id[:8])
|
||||||
|
manager1Received := *s.Instances[manager1]
|
||||||
|
assert.Equal(t, Instance{
|
||||||
|
Name: manager1,
|
||||||
|
Image: "franela/dind",
|
||||||
|
Hostname: "manager1",
|
||||||
|
IP: "10.0.0.1",
|
||||||
|
Alias: "",
|
||||||
|
IsDockerHost: true,
|
||||||
|
session: s,
|
||||||
|
conn: manager1Received.conn,
|
||||||
|
docker: manager1Received.docker,
|
||||||
|
}, manager1Received)
|
||||||
|
|
||||||
|
manager2 := fmt.Sprintf("%s_manager2", s.Id[:8])
|
||||||
|
manager2Received := *s.Instances[manager2]
|
||||||
|
assert.Equal(t, Instance{
|
||||||
|
Name: manager2,
|
||||||
|
Image: "franela/dind",
|
||||||
|
Hostname: "manager2",
|
||||||
|
IP: "10.0.0.2",
|
||||||
|
Alias: "",
|
||||||
|
IsDockerHost: true,
|
||||||
|
session: s,
|
||||||
|
conn: manager2Received.conn,
|
||||||
|
docker: manager2Received.docker,
|
||||||
|
}, manager2Received)
|
||||||
|
|
||||||
|
manager3 := fmt.Sprintf("%s_manager3", s.Id[:8])
|
||||||
|
manager3Received := *s.Instances[manager3]
|
||||||
|
assert.Equal(t, Instance{
|
||||||
|
Name: manager3,
|
||||||
|
Image: "franela/dind:overlay2-dev",
|
||||||
|
Hostname: "manager3",
|
||||||
|
IP: "10.0.0.3",
|
||||||
|
Alias: "",
|
||||||
|
IsDockerHost: true,
|
||||||
|
session: s,
|
||||||
|
conn: manager3Received.conn,
|
||||||
|
docker: manager3Received.docker,
|
||||||
|
}, manager3Received)
|
||||||
|
|
||||||
|
worker1 := fmt.Sprintf("%s_worker1", s.Id[:8])
|
||||||
|
worker1Received := *s.Instances[worker1]
|
||||||
|
assert.Equal(t, Instance{
|
||||||
|
Name: worker1,
|
||||||
|
Image: "franela/dind",
|
||||||
|
Hostname: "worker1",
|
||||||
|
IP: "10.0.0.4",
|
||||||
|
Alias: "",
|
||||||
|
IsDockerHost: true,
|
||||||
|
session: s,
|
||||||
|
conn: worker1Received.conn,
|
||||||
|
docker: worker1Received.docker,
|
||||||
|
}, worker1Received)
|
||||||
|
|
||||||
|
other := fmt.Sprintf("%s_other", s.Id[:8])
|
||||||
|
otherReceived := *s.Instances[other]
|
||||||
|
assert.Equal(t, Instance{
|
||||||
|
Name: other,
|
||||||
|
Image: "franela/dind",
|
||||||
|
Hostname: "other",
|
||||||
|
IP: "10.0.0.5",
|
||||||
|
Alias: "",
|
||||||
|
IsDockerHost: true,
|
||||||
|
session: s,
|
||||||
|
conn: otherReceived.conn,
|
||||||
|
docker: otherReceived.docker,
|
||||||
|
}, otherReceived)
|
||||||
|
|
||||||
|
assert.True(t, swarmInitOnMaster1)
|
||||||
|
assert.True(t, manager2JoinedHasManager)
|
||||||
|
assert.True(t, manager3JoinedHasManager)
|
||||||
|
assert.True(t, worker1JoinedHasWorker)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService) {
|
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', 'SessionService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService, SessionService) {
|
||||||
$scope.sessionId = window.location.pathname.replace('/p/', '');
|
$scope.sessionId = SessionService.getCurrentSessionId();
|
||||||
$scope.instances = [];
|
$scope.instances = [];
|
||||||
$scope.idx = {};
|
$scope.idx = {};
|
||||||
$scope.selectedInstance = null;
|
$scope.selectedInstance = null;
|
||||||
@@ -363,7 +363,7 @@
|
|||||||
$mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24);
|
$mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24);
|
||||||
}])
|
}])
|
||||||
.component('settingsIcon', {
|
.component('settingsIcon', {
|
||||||
template : "<md-button ng-click='$ctrl.onClick()'><md-icon class='material-icons'>settings</md-icon></md-button>",
|
template : "<md-button class='md-mini' ng-click='$ctrl.onClick()'><md-icon class='material-icons'>settings</md-icon></md-button>",
|
||||||
controller : function($mdDialog) {
|
controller : function($mdDialog) {
|
||||||
var $ctrl = this;
|
var $ctrl = this;
|
||||||
$ctrl.onClick = function() {
|
$ctrl.onClick = function() {
|
||||||
@@ -376,6 +376,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.component('templatesIcon', {
|
||||||
|
template : "<md-button class='md-mini' ng-click='$ctrl.onClick()'><md-icon class='material-icons'>build</md-icon></md-button>",
|
||||||
|
controller : function($mdDialog) {
|
||||||
|
var $ctrl = this;
|
||||||
|
$ctrl.onClick = function() {
|
||||||
|
$mdDialog.show({
|
||||||
|
controller : function() {},
|
||||||
|
template : "<templates-dialog></templates-dialog>",
|
||||||
|
parent: angular.element(document.body),
|
||||||
|
clickOutsideToClose : true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.component("templatesDialog", {
|
||||||
|
templateUrl : "templates-modal.html",
|
||||||
|
controller : function($mdDialog, $scope, SessionService) {
|
||||||
|
var $ctrl = this;
|
||||||
|
$scope.building = false;
|
||||||
|
$scope.templates = SessionService.getAvailableTemplates();
|
||||||
|
$ctrl.close = function() {
|
||||||
|
$mdDialog.cancel();
|
||||||
|
}
|
||||||
|
$ctrl.setupSession = function(setup) {
|
||||||
|
$scope.building = true;
|
||||||
|
SessionService.setup(setup, function(err) {
|
||||||
|
$scope.building = false;
|
||||||
|
if (err) {
|
||||||
|
$scope.errorMessage = err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$ctrl.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.component("settingsDialog", {
|
.component("settingsDialog", {
|
||||||
templateUrl : "settings-modal.html",
|
templateUrl : "settings-modal.html",
|
||||||
controller : function($mdDialog, KeyboardShortcutService, $rootScope, InstanceService, TerminalService) {
|
controller : function($mdDialog, KeyboardShortcutService, $rootScope, InstanceService, TerminalService) {
|
||||||
@@ -419,6 +455,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.service("SessionService", function($http) {
|
||||||
|
var templates = [
|
||||||
|
{
|
||||||
|
title: '3 Managers and 2 Workers',
|
||||||
|
icon: '/assets/swarm.png',
|
||||||
|
setup: {
|
||||||
|
instances: [
|
||||||
|
{hostname: 'manager1', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager2', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager3', is_swarm_manager: true},
|
||||||
|
{hostname: 'worker1', is_swarm_worker: true},
|
||||||
|
{hostname: 'worker2', is_swarm_worker: true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '5 Managers and no workers',
|
||||||
|
icon: '/assets/swarm.png',
|
||||||
|
setup: {
|
||||||
|
instances: [
|
||||||
|
{hostname: 'manager1', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager2', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager3', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager4', is_swarm_manager: true},
|
||||||
|
{hostname: 'manager5', is_swarm_manager: true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
getAvailableTemplates: getAvailableTemplates,
|
||||||
|
getCurrentSessionId: getCurrentSessionId,
|
||||||
|
setup: setup,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCurrentSessionId() {
|
||||||
|
return window.location.pathname.replace('/p/', '');
|
||||||
|
}
|
||||||
|
function getAvailableTemplates() {
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
function setup(plan, cb) {
|
||||||
|
return $http
|
||||||
|
.post("/sessions/" + getCurrentSessionId() + "/setup", plan)
|
||||||
|
.then(function(response) {
|
||||||
|
if (cb) cb();
|
||||||
|
}, function(response) {
|
||||||
|
if (cb) cb(response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.service("InstanceService", function($http) {
|
.service("InstanceService", function($http) {
|
||||||
var instanceImages = [];
|
var instanceImages = [];
|
||||||
_prepopulateAvailableImages();
|
_prepopulateAvailableImages();
|
||||||
|
|||||||
@@ -59,3 +59,6 @@ md-input-container .md-errors-spacer {
|
|||||||
background-color: rgba(0,0,0,.5);
|
background-color: rgba(0,0,0,.5);
|
||||||
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
|
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
|
||||||
}
|
}
|
||||||
|
.md-mini {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
|||||||
BIN
www/assets/swarm.png
Normal file
BIN
www/assets/swarm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -47,6 +47,7 @@
|
|||||||
<md-button class="md-warn md-raised" ng-click="closeSession()">Close session</md-button>
|
<md-button class="md-warn md-raised" ng-click="closeSession()">Close session</md-button>
|
||||||
<div class="md-toolbar-tools">
|
<div class="md-toolbar-tools">
|
||||||
<h1 class="md-toolbar-tools">Instances</h1>
|
<h1 class="md-toolbar-tools">Instances</h1>
|
||||||
|
<templates-icon></templates-icon>
|
||||||
<settings-icon></settings-icon>
|
<settings-icon></settings-icon>
|
||||||
</div>
|
</div>
|
||||||
</md-toolbar>
|
</md-toolbar>
|
||||||
@@ -145,6 +146,49 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="templates-modal.html">
|
||||||
|
<md-toolbar>
|
||||||
|
<div class="md-toolbar-tools">
|
||||||
|
<h2>Templates</h2>
|
||||||
|
<span flex></span>
|
||||||
|
<md-button class="md-icon-button" ng-click="$ctrl.close()">
|
||||||
|
<md-icon class="material-icon" aria-label="Close dialog">close</md-icon>
|
||||||
|
</md-button>
|
||||||
|
</div>
|
||||||
|
</md-toolbar>
|
||||||
|
|
||||||
|
<md-dialog-content>
|
||||||
|
<div class="md-dialog-content" style="width:600px;">
|
||||||
|
<div layout="row" layout-sm="column" layout-align="space-around" ng-if="building">
|
||||||
|
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div layout="row" ng-if="errorMessage">
|
||||||
|
<div flex="100" style="margin-top: 20px; text-align:center; font-weight: bold; color: red;">
|
||||||
|
{{errorMessage}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<md-list flex ng-if="!building">
|
||||||
|
<md-list-item class="md-3-line" ng-repeat="template in templates" ng-click="$ctrl.setupSession(template.setup)">
|
||||||
|
<md-card md-theme="default" md-theme-watch>
|
||||||
|
<md-card-title>
|
||||||
|
<md-card-title-text>
|
||||||
|
<span class="md-headline">{{template.title}}</span>
|
||||||
|
</md-card-title-text>
|
||||||
|
<md-card-title-media>
|
||||||
|
<div class="md-media-sm card-media"><img ng-src="{{template.icon}}" style="height: 75px;" class="md-card-image"></div>
|
||||||
|
</md-card-title-media>
|
||||||
|
</md-card-title>
|
||||||
|
</md-card>
|
||||||
|
</md-list-item>
|
||||||
|
</md-list>
|
||||||
|
</md-dialog-content>
|
||||||
|
<md-dialog-actions layout="row">
|
||||||
|
<span flex></span>
|
||||||
|
<md-button ng-click="$ctrl.close()">
|
||||||
|
Close
|
||||||
|
</md-button>
|
||||||
|
</md-dialog-actions>
|
||||||
|
</script>
|
||||||
<script type="text/ng-template" id="settings-modal.html">
|
<script type="text/ng-template" id="settings-modal.html">
|
||||||
<md-toolbar>
|
<md-toolbar>
|
||||||
<div class="md-toolbar-tools">
|
<div class="md-toolbar-tools">
|
||||||
|
|||||||
Reference in New Issue
Block a user