Merge pull request #1966 from weaveworks/746-resize-ttys

Resize TTYs
This commit is contained in:
Simon
2016-11-03 11:06:16 +01:00
committed by GitHub
59 changed files with 4393 additions and 116 deletions

View File

@@ -20,6 +20,7 @@ const (
RemoveContainer = "docker_remove_container"
AttachContainer = "docker_attach_container"
ExecContainer = "docker_exec_container"
ResizeExecTTY = "docker_resize_exec_tty"
waitTime = 10
)
@@ -122,6 +123,7 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
if err != nil {
return xfer.ResponseError(err)
}
local, _ := pipe.Ends()
cw, err := r.client.StartExecNonBlocking(exec.ID, docker_client.StartExecOptions{
Tty: true,
@@ -133,11 +135,19 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
if err != nil {
return xfer.ResponseError(err)
}
r.Lock()
r.pipeIDToexecID[id] = exec.ID
r.Unlock()
pipe.OnClose(func() {
if err := cw.Close(); err != nil {
log.Errorf("Error closing exec in container %s: %v", containerID, err)
return
}
r.Lock()
delete(r.pipeIDToexecID, id)
r.Unlock()
})
go func() {
if err := cw.Wait(); err != nil {
@@ -146,11 +156,30 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
pipe.Close()
}()
return xfer.Response{
Pipe: id,
RawTTY: true,
Pipe: id,
RawTTY: true,
ResizeTTYControl: ResizeExecTTY,
}
}
func (r *registry) resizeExecTTY(pipeID string, height, width uint) xfer.Response {
r.Lock()
execID, ok := r.pipeIDToexecID[pipeID]
r.Unlock()
if !ok {
return xfer.ResponseErrorf("Unknown pipeID (%q)", pipeID)
}
if err := r.client.ResizeExecTTY(execID, int(height), int(width)); err != nil {
return xfer.ResponseErrorf(
"Error setting terminal size (%d, %d) of pipe %s: %v",
height, width, pipeID, err)
}
return xfer.Response{}
}
func captureContainerID(f func(string, xfer.Request) xfer.Response) func(xfer.Request) xfer.Response {
return func(req xfer.Request) xfer.Response {
containerID, ok := report.ParseContainerNodeID(req.NodeID)
@@ -171,6 +200,7 @@ func (r *registry) registerControls() {
RemoveContainer: captureContainerID(r.removeContainer),
AttachContainer: captureContainerID(r.attachContainer),
ExecContainer: captureContainerID(r.execContainer),
ResizeExecTTY: xfer.ResizeTTYControlWrapper(r.resizeExecTTY),
}
r.handlerRegistry.Batch(nil, controls)
}
@@ -185,6 +215,7 @@ func (r *registry) deregisterControls() {
RemoveContainer,
AttachContainer,
ExecContainer,
ResizeExecTTY,
}
r.handlerRegistry.Batch(controls, nil)
}

View File

@@ -66,20 +66,33 @@ func TestPipes(t *testing.T) {
return ok
})
for _, tc := range []string{
docker.AttachContainer,
docker.ExecContainer,
for _, want := range []struct {
control string
response xfer.Response
}{
{
control: docker.AttachContainer,
response: xfer.Response{
Pipe: "pipeid",
RawTTY: true,
},
},
{
control: docker.ExecContainer,
response: xfer.Response{
Pipe: "pipeid",
RawTTY: true,
ResizeTTYControl: docker.ResizeExecTTY,
},
},
} {
result := hr.HandleControlRequest(xfer.Request{
Control: tc,
Control: want.control,
NodeID: report.MakeContainerNodeID("ping"),
})
want := xfer.Response{
Pipe: "pipeid",
RawTTY: true,
}
if !reflect.DeepEqual(result, want) {
t.Errorf("diff %s: %s", tc, test.Diff(want, result))
if !reflect.DeepEqual(result, want.response) {
t.Errorf("diff %s: %s", want.control, test.Diff(want, result))
}
}
})

View File

@@ -65,6 +65,7 @@ type registry struct {
containersByPID map[int]Container
images map[string]docker_client.APIImages
networks []docker_client.Network
pipeIDToexecID map[string]string
}
// Client interface for mocking.
@@ -86,6 +87,7 @@ type Client interface {
CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error)
StartExecNonBlocking(string, docker_client.StartExecOptions) (docker_client.CloseWaiter, error)
Stats(docker_client.StatsOptions) error
ResizeExecTTY(id string, height, width int) error
}
func newDockerClient(endpoint string) (Client, error) {
@@ -103,6 +105,7 @@ func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats
containers: radix.New(),
containersByPID: map[int]Container{},
images: map[string]docker_client.APIImages{},
pipeIDToexecID: map[string]string{},
client: client,
pipes: pipes,

View File

@@ -167,6 +167,10 @@ func (m *mockDockerClient) Stats(_ client.StatsOptions) error {
return fmt.Errorf("stats")
}
func (m *mockDockerClient) ResizeExecTTY(id string, height, width int) error {
return fmt.Errorf("resizeExecTTY")
}
type mockCloseWaiter struct{}
func (mockCloseWaiter) Close() error { return nil }

View File

@@ -4,6 +4,7 @@ import (
"os/exec"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/term"
"github.com/kr/pty"
"github.com/weaveworks/scope/common/xfer"
@@ -12,15 +13,18 @@ import (
// Control IDs used by the host integration.
const (
ExecHost = "host_exec"
ExecHost = "host_exec"
ResizeExecTTY = "host_resize_exec_tty"
)
func (r *Reporter) registerControls() {
r.handlerRegistry.Register(ExecHost, r.execHost)
r.handlerRegistry.Register(ResizeExecTTY, xfer.ResizeTTYControlWrapper(r.resizeExecTTY))
}
func (r *Reporter) deregisterControls() {
r.handlerRegistry.Rm(ExecHost)
r.handlerRegistry.Rm(ResizeExecTTY)
}
func (r *Reporter) execHost(req xfer.Request) xfer.Response {
@@ -35,6 +39,11 @@ func (r *Reporter) execHost(req xfer.Request) xfer.Response {
if err != nil {
return xfer.ResponseError(err)
}
r.Lock()
r.pipeIDToTTY[id] = ptyPipe.Fd()
r.Unlock()
pipe.OnClose(func() {
if err := cmd.Process.Kill(); err != nil {
log.Errorf("Error stopping host shell: %v", err)
@@ -42,6 +51,9 @@ func (r *Reporter) execHost(req xfer.Request) xfer.Response {
if err := ptyPipe.Close(); err != nil {
log.Errorf("Error closing host shell's pty: %v", err)
}
r.Lock()
delete(r.pipeIDToTTY, id)
r.Unlock()
log.Info("Host shell closed.")
})
go func() {
@@ -52,7 +64,32 @@ func (r *Reporter) execHost(req xfer.Request) xfer.Response {
}()
return xfer.Response{
Pipe: id,
RawTTY: true,
Pipe: id,
RawTTY: true,
ResizeTTYControl: ResizeExecTTY,
}
}
func (r *Reporter) resizeExecTTY(pipeID string, height, width uint) xfer.Response {
r.Lock()
fd, ok := r.pipeIDToTTY[pipeID]
r.Unlock()
if !ok {
return xfer.ResponseErrorf("Unknown pipeID (%q)", pipeID)
}
size := term.Winsize{
Height: uint16(height),
Width: uint16(width),
}
if err := term.SetWinsize(fd, &size); err != nil {
return xfer.ResponseErrorf(
"Error setting terminal size (%d, %d) of pipe %s: %v",
height, width, pipeID, err)
}
return xfer.Response{}
}

View File

@@ -3,6 +3,7 @@ package host
import (
"net"
"runtime"
"sync"
"time"
"github.com/weaveworks/scope/common/mtime"
@@ -52,6 +53,7 @@ var (
// Reporter generates Reports containing the host topology.
type Reporter struct {
sync.RWMutex
hostID string
hostName string
probeID string
@@ -59,6 +61,7 @@ type Reporter struct {
pipes controls.PipeClient
hostShellCmd []string
handlerRegistry *controls.HandlerRegistry
pipeIDToTTY map[string]uintptr
}
// NewReporter returns a Reporter which produces a report containing host
@@ -72,13 +75,14 @@ func NewReporter(hostID, hostName, probeID, version string, pipes controls.PipeC
version: version,
hostShellCmd: getHostShellCmd(),
handlerRegistry: handlerRegistry,
pipeIDToTTY: map[string]uintptr{},
}
r.registerControls()
return r
}
// Name of this reporter, for metrics gathering
func (Reporter) Name() string { return "Host" }
func (*Reporter) Name() string { return "Host" }
// GetLocalNetworks is exported for mocking
var GetLocalNetworks = func() ([]*net.IPNet, error) {