1
0
mirror of https://github.com/bingohuang/docker-labs.git synced 2025-07-14 01:57:32 +08:00

Merge pull request #32 from franela/session_persist

Session persist
This commit is contained in:
Marcos Nils 2016-11-15 02:33:31 +02:00 committed by GitHub
commit 6a0e07aeec
6 changed files with 101 additions and 54 deletions

View File

@ -5,4 +5,5 @@ RUN apk add --no-cache git tmux py-pip apache2-utils vim \
COPY vimrc ./root/.vimrc
CMD docker daemon --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 --storage-driver=vfs &>- & /bin/sh

7
api.go
View File

@ -3,6 +3,7 @@ package main
import (
"log"
"net/http"
"os"
"github.com/franela/play-with-docker/handlers"
"github.com/franela/play-with-docker/services"
@ -11,11 +12,17 @@ import (
)
func main() {
server := services.CreateWSServer()
server.On("connection", handlers.WS)
server.On("error", handlers.WSError)
err := services.LoadSessionsFromDisk()
if err != nil && !os.IsNotExist(err) {
log.Fatal("Error decoding sessions from disk ", err)
}
r := mux.NewRouter()
r.StrictSlash(false)

View File

@ -12,7 +12,8 @@ type ViewPort struct {
}
type Client struct {
SO socketio.Socket
Id string
so socketio.Socket
ViewPort ViewPort
}
@ -24,7 +25,7 @@ func (c *Client) ResizeViewPort(cols, rows uint) {
func NewClient(so socketio.Socket, session *Session) *Client {
so.Join(session.Id)
c := &Client{SO: so}
c := &Client{so: so, Id: so.Id()}
so.On("session close", func() {
CloseSession(session)
@ -33,8 +34,8 @@ func NewClient(so socketio.Socket, session *Session) *Client {
so.On("terminal in", func(name, data string) {
// User wrote something on the terminal. Need to write it to the instance terminal
instance := GetInstance(session, name)
if instance != nil && len(data) > 0 {
instance.Conn.Conn.Write([]byte(data))
if instance != nil && instance.conn != nil && len(data) > 0 {
instance.conn.Conn.Write([]byte(data))
}
})
@ -53,13 +54,13 @@ func NewClient(so socketio.Socket, session *Session) *Client {
})
so.On("disconnection", func() {
// Client has disconnected. Remove from session and recheck terminal sizes.
for i, cl := range session.Clients {
if cl.SO.Id() == c.SO.Id() {
session.Clients = append(session.Clients[:i], session.Clients[i+1:]...)
for i, cl := range session.clients {
if cl.Id == c.Id {
session.clients = append(session.clients[:i], session.clients[i+1:]...)
break
}
}
if len(session.Clients) > 0 {
if len(session.clients) > 0 {
vp := session.GetSmallestViewPort()
// Resize all terminals in the session
wsServer.BroadcastTo(session.Id, "viewport resize", vp.Cols, vp.Rows)

View File

@ -55,20 +55,10 @@ func DeleteNetwork(id string) error {
return nil
}
func CreateExecConnection(id string, ctx context.Context) (string, error) {
conf := types.ExecConfig{Tty: true, AttachStdin: true, AttachStderr: true, AttachStdout: true, Cmd: []string{"sh"}, DetachKeys: "ctrl-x,ctrl-x"}
resp, err := c.ContainerExecCreate(ctx, id, conf)
if err != nil {
return "", err
}
return resp.ID, nil
}
func AttachExecConnection(execId string, ctx context.Context) (*types.HijackedResponse, error) {
conf := types.ExecConfig{Tty: true, AttachStdin: true, AttachStderr: true, AttachStdout: true, DetachKeys: "ctrl-x,ctrl-x"}
conn, err := c.ContainerExecAttach(ctx, execId, conf)
func CreateAttachConnection(id string, ctx context.Context) (*types.HijackedResponse, error) {
conf := types.ContainerAttachOptions{true, true, true, true, "ctrl-x,ctrl-x", true}
conn, err := c.ContainerAttach(ctx, id, conf)
if err != nil {
return nil, err
}
@ -76,8 +66,8 @@ func AttachExecConnection(execId string, ctx context.Context) (*types.HijackedRe
return &conn, nil
}
func ResizeExecConnection(execId string, ctx context.Context, cols, rows uint) error {
return c.ContainerExecResize(ctx, execId, types.ResizeOptions{Height: rows, Width: cols})
func ResizeConnection(name string, cols, rows uint) error {
return c.ContainerResize(context.Background(), name, types.ResizeOptions{Height: rows, Width: cols})
}
func CreateInstance(net string, dindImage string) (*Instance, error) {
@ -88,7 +78,7 @@ func CreateInstance(net string, dindImage string) (*Instance, error) {
t := true
h.Resources.OomKillDisable = &t
conf := &container.Config{Image: dindImage, Tty: true}
conf := &container.Config{Image: dindImage, Tty: true, OpenStdin: true, AttachStdin: true, AttachStdout: true, AttachStderr: true}
container, err := c.ContainerCreate(context.Background(), conf, h, nil, "")
if err != nil {

View File

@ -5,20 +5,31 @@ import (
"io"
"log"
"os"
"sync"
"golang.org/x/text/encoding"
"github.com/docker/docker/api/types"
)
var rw sync.Mutex
type Instance struct {
Session *Session `json:"-"`
session *Session `json:"-"`
Name string `json:"name"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
Conn *types.HijackedResponse `json:"-"`
ExecId string `json:"-"`
Ctx context.Context `json:"-"`
conn *types.HijackedResponse `json:"-"`
ctx context.Context `json:"-"`
}
func (i *Instance) IsConnected() bool {
return i.conn != nil
}
func (i *Instance) SetSession(s *Session) {
i.session = s
}
var dindImage string
@ -43,14 +54,18 @@ func NewInstance(session *Session) (*Instance, error) {
if err != nil {
return nil, err
}
instance.Session = session
instance.session = session
if session.Instances == nil {
session.Instances = make(map[string]*Instance)
}
session.Instances[instance.Name] = instance
go instance.Exec()
go instance.Attach()
rw.Lock()
err = saveSessionsToDisk()
rw.Unlock()
wsServer.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
@ -62,28 +77,23 @@ type sessionWriter struct {
}
func (s *sessionWriter) Write(p []byte) (n int, err error) {
wsServer.BroadcastTo(s.instance.Session.Id, "terminal out", s.instance.Name, string(p))
wsServer.BroadcastTo(s.instance.session.Id, "terminal out", s.instance.Name, string(p))
return len(p), nil
}
func (i *Instance) ResizeTerminal(cols, rows uint) error {
return ResizeExecConnection(i.ExecId, i.Ctx, cols, rows)
return ResizeConnection(i.Name, cols, rows)
}
func (i *Instance) Exec() {
i.Ctx = context.Background()
func (i *Instance) Attach() {
i.ctx = context.Background()
conn, err := CreateAttachConnection(i.Name, i.ctx)
id, err := CreateExecConnection(i.Name, i.Ctx)
if err != nil {
return
}
i.ExecId = id
conn, err := AttachExecConnection(id, i.Ctx)
if err != nil {
return
}
i.Conn = conn
i.conn = conn
go func() {
encoder := encoding.Replacement.NewEncoder()
@ -92,10 +102,9 @@ func (i *Instance) Exec() {
}()
select {
case <-i.Ctx.Done():
case <-i.ctx.Done():
}
}
func GetInstance(session *Session, name string) *Instance {
//TODO: Use redis
return session.Instances[name]

View File

@ -1,8 +1,10 @@
package services
import (
"encoding/gob"
"log"
"math"
"os"
"sync"
"time"
@ -13,17 +15,25 @@ import (
var wsServer *socketio.Server
type Session struct {
sync.Mutex
rw sync.Mutex
Id string `json:"id"`
Instances map[string]*Instance `json:"instances"`
Clients []*Client `json:"-"`
clients []*Client `json:"-"`
}
func (s *Session) Lock() {
s.rw.Lock()
}
func (s *Session) Unlock() {
s.rw.Unlock()
}
func (s *Session) GetSmallestViewPort() ViewPort {
minRows := s.Clients[0].ViewPort.Rows
minCols := s.Clients[0].ViewPort.Cols
minRows := s.clients[0].ViewPort.Rows
minCols := s.clients[0].ViewPort.Cols
for _, c := range s.Clients {
for _, c := range s.clients {
minRows = uint(math.Min(float64(minRows), float64(c.ViewPort.Rows)))
minCols = uint(math.Min(float64(minCols), float64(c.ViewPort.Cols)))
}
@ -32,7 +42,7 @@ func (s *Session) GetSmallestViewPort() ViewPort {
}
func (s *Session) AddNewClient(c *Client) {
s.Clients = append(s.Clients, c)
s.clients = append(s.clients, c)
}
var sessions map[string]*Session
@ -51,12 +61,12 @@ func CreateWSServer() *socketio.Server {
}
func CloseSession(s *Session) error {
s.Lock()
defer s.Unlock()
s.rw.Lock()
defer s.rw.Unlock()
wsServer.BroadcastTo(s.Id, "session end")
log.Printf("Starting clean up of session [%s]\n", s.Id)
for _, i := range s.Instances {
i.Conn.Close()
i.conn.Close()
if err := DeleteContainer(i.Name); err != nil {
log.Println(err)
return err
@ -88,10 +98,39 @@ func NewSession() (*Session, error) {
log.Println("ERROR NETWORKING")
return nil, err
}
return s, nil
}
func GetSession(sessionId string) *Session {
return sessions[sessionId]
s := sessions[sessionId]
if s != nil {
for _, instance := range s.Instances {
if !instance.IsConnected() {
instance.SetSession(s)
go instance.Attach()
}
}
}
return s
}
func LoadSessionsFromDisk() error {
file, err := os.Open("./sessions.gob")
if err == nil {
decoder := gob.NewDecoder(file)
err = decoder.Decode(&sessions)
}
file.Close()
return err
}
func saveSessionsToDisk() error {
file, err := os.Create("./sessions.gob")
if err == nil {
encoder := gob.NewEncoder(file)
err = encoder.Encode(&sessions)
}
file.Close()
return err
}