Files
virtual-kubelet/vendor/github.com/vmware/vic/cmd/tether/attach_test.go
Loc Nguyen 513cebe7b7 VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
2018-06-04 15:41:32 -07:00

1097 lines
26 KiB
Go

// Copyright 2016 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
"os"
"path"
"sync"
"syscall"
"testing"
"time"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ssh"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/lib/portlayer/attach/communication"
"github.com/vmware/vic/lib/migration/feature"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/serial"
)
type testAttachServer struct {
attachServerSSH
enabled bool
updated chan bool
}
func (t *testAttachServer) start() error {
t.testing = true
err := t.attachServerSSH.start()
if err == nil {
t.updated <- true
t.enabled = true
}
log.Info("Started test attach server")
return err
}
func (t *testAttachServer) stop() error {
if t.enabled {
err := t.attachServerSSH.stop()
if err == nil {
log.Info("Stopped test attach server")
t.updated <- true
t.enabled = false
}
return err
}
return nil
}
func (t *testAttachServer) Reload(config *tether.ExecutorConfig) error {
log.Info("Parsing config in test attach server")
return t.attachServerSSH.Reload(config)
}
func (t *testAttachServer) Start() error {
log.Info("opening ttyS0 pipe pair for backchannel (server)")
c, err := os.OpenFile(path.Join(pathPrefix, "ttyS0c"), os.O_WRONLY|syscall.O_NOCTTY, 0777)
if err != nil {
detail := fmt.Sprintf("failed to open cpipe for backchannel: %s", err)
log.Error(detail)
return errors.New(detail)
}
s, err := os.OpenFile(path.Join(pathPrefix, "ttyS0s"), os.O_RDONLY|syscall.O_NOCTTY, 0777)
if err != nil {
detail := fmt.Sprintf("failed to open spipe for backchannel: %s", err)
log.Error(detail)
return errors.New(detail)
}
log.Infof("creating raw connection from ttyS0 pipe pair for server (c=%d, s=%d) %s\n", c.Fd(), s.Fd(), pathPrefix)
conn, err := serial.NewHalfDuplexFileConn(s, c, path.Join(pathPrefix, "ttyS0"), "file")
if err != nil {
detail := fmt.Sprintf("failed to create raw connection from ttyS0 pipe pair: %s", err)
log.Error(detail)
return errors.New(detail)
}
t.conn.Lock()
defer t.conn.Unlock()
t.conn.conn = conn
return nil
}
func (t *testAttachServer) Stop() error {
return t.attachServerSSH.Stop()
}
// create client on the mock pipe
func mockBackChannel(ctx context.Context) (net.Conn, error) {
log.Info("opening ttyS0 pipe pair for backchannel (client)")
c, err := os.OpenFile(path.Join(pathPrefix, "ttyS0c"), os.O_RDONLY|syscall.O_NOCTTY, 0777)
if err != nil {
detail := fmt.Sprintf("failed to open cpipe for backchannel: %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
s, err := os.OpenFile(path.Join(pathPrefix, "ttyS0s"), os.O_WRONLY|syscall.O_NOCTTY, 0777)
if err != nil {
detail := fmt.Sprintf("failed to open spipe for backchannel: %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
log.Infof("creating raw connection from ttyS0 pipe pair for backchannel (c=%d, s=%d) %s\n", c.Fd(), s.Fd(), pathPrefix)
conn, err := serial.NewHalfDuplexFileConn(c, s, path.Join(pathPrefix, "ttyS0"), "file")
if err != nil {
detail := fmt.Sprintf("failed to create raw connection from ttyS0 pipe pair: %s", err)
log.Error(detail)
return nil, errors.New(detail)
}
// HACK: currently RawConn dosn't implement timeout so throttle the spinning
ticker := time.NewTicker(1000 * time.Millisecond)
for {
select {
case <-ticker.C:
err := serial.HandshakeClient(conn)
if err != nil {
if err == io.EOF {
// with unix pipes the open will block until both ends are open, therefore
// EOF means the other end has been intentionally closed
return nil, err
}
log.Error(err)
} else {
return conn, nil
}
case <-ctx.Done():
conn.Close()
ticker.Stop()
return nil, ctx.Err()
}
}
}
// create client on the mock pipe and dial the given host:port
func mockNetworkToSerialConnection(host string) (*sync.WaitGroup, error) {
log.Info("opening ttyS0 pipe pair for backchannel")
c, err := os.OpenFile(path.Join(pathPrefix, "ttyS0c"), os.O_RDONLY|syscall.O_NOCTTY, 0777)
if err != nil {
return nil, fmt.Errorf("failed to open cpipe for backchannel: %s", err)
}
s, err := os.OpenFile(path.Join(pathPrefix, "ttyS0s"), os.O_WRONLY|syscall.O_NOCTTY, 0777)
if err != nil {
return nil, fmt.Errorf("failed to open spipe for backchannel: %s", err)
}
log.Infof("creating raw connection from ttyS0 pipe pair (c=%d, s=%d)\n", c.Fd(), s.Fd())
conn, err := serial.NewHalfDuplexFileConn(c, s, path.Join(pathPrefix, "/ttyS0"), "file")
if err != nil {
return nil, fmt.Errorf("failed to create raw connection from ttyS0 pipe pair: %s", err)
}
// Dial the attach server. This is a TCP client
networkClientCon, err := net.Dial("tcp", host)
if err != nil {
return nil, err
}
log.Debugf("dialed %s", host)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
io.Copy(networkClientCon, conn)
wg.Done()
}()
go func() {
io.Copy(conn, networkClientCon)
wg.Done()
}()
return &wg, nil
}
func genKey() []byte {
// generate a host key for the tether
privateKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
panic("unable to generate private key during test")
}
privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privateKeyDer,
}
return pem.EncodeToMemory(&privateKeyBlock)
}
func attachCase(t *testing.T, runblock bool) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
Diagnostics: executor.Diagnostics{
DebugLevel: 1,
},
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "attach",
Name: "tether_test_executor",
},
Sessions: map[string]*executor.SessionConfig{
"attach": {
Common: executor.Common{
ID: "attach",
Name: "tether_test_session",
},
Tty: false,
Attach: true,
Active: true,
OpenStdin: true,
RunBlock: runblock,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
containerConfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client from the mocked connection
sshConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", containerConfig)
assert.NoError(t, err)
defer sshConn.Close()
ssh.NewClient(sshConn, chans, reqs)
_, err = communication.ContainerIDs(sshConn)
version, err := communication.ContainerVersion(sshConn)
assert.NoError(t, err)
sshSession, err := communication.NewSSHInteraction(sshConn, cfg.ID, version)
if runblock {
sshSession.Unblock()
}
assert.NoError(t, err)
stdout := sshSession.Stdout()
// FIXME: the pipe pair are line buffered - how do I disable that so we
// don't have odd hangs to diagnose when the trailing \n is missed
testBytes := []byte("\x1b[32mhello world\x1b[39m!\n")
// read from session into buffer
buf := &bytes.Buffer{}
done := make(chan bool)
go func() { io.Copy(buf, stdout); done <- true }()
// write something to echo
log.Debug("sending test data")
sshSession.Stdin().Write(testBytes)
log.Debug("sent test data")
// wait for the close to propagate
sshSession.CloseStdin()
<-done
// sshSession.Close()
}
func TestAttach(t *testing.T) {
attachCase(t, false)
}
func TestAttachBlock(t *testing.T) {
attachCase(t, true)
}
//
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// TestAttachTTYConfig sets up the config for attach testing
//
func TestAttachTTY(t *testing.T) {
t.Skip("TTY test skipped - not sure how to test this correctly")
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "attach",
Name: "tether_test_executor",
},
Sessions: map[string]*executor.SessionConfig{
"attach": {
Common: executor.Common{
ID: "attach",
Name: "tether_test_session",
},
Tty: true,
Attach: true,
Active: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
cconfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client
sConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", cconfig)
assert.NoError(t, err)
defer sConn.Close()
client := ssh.NewClient(sConn, chans, reqs)
session, err := communication.NewSSHInteraction(client, cfg.ID, feature.MaxPluginVersion-1)
assert.NoError(t, err)
stdout := session.Stdout()
// FIXME: this is line buffered - how do I disable that so we don't have odd hangs to diagnose
// when the trailing \n is missed
testBytes := []byte("\x1b[32mhello world\x1b[39m!\n")
// after tty translation the above string should result in the following
refBytes := []byte("\x5e[[32mhello world\x5e[[39m!\n")
// read from session into buffer
buf := &bytes.Buffer{}
var wg sync.WaitGroup
wg.Add(1)
go func() {
io.CopyN(buf, stdout, int64(len(refBytes)))
wg.Done()
}()
// write something to echo
log.Debug("sending test data")
session.Stdin().Write(testBytes)
log.Debug("sent test data")
// wait for the close to propagate
wg.Wait()
session.CloseStdin()
assert.Equal(t, refBytes, buf.Bytes())
}
//
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// TestAttachTTYStdinClose sets up the config for attach testing
//
func TestAttachTTYStdinClose(t *testing.T) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "sort",
Name: "tether_test_executor",
},
Diagnostics: executor.Diagnostics{
DebugLevel: 1,
},
Sessions: map[string]*executor.SessionConfig{
"sort": {
Common: executor.Common{
ID: "sort",
Name: "tether_test_session",
},
Tty: true,
Attach: true,
Active: true,
OpenStdin: true,
RunBlock: true,
Cmd: executor.Cmd{
Path: "/usr/bin/sort",
// reading from stdin
Args: []string{"/usr/bin/sort"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
containerConfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client from the mocked connection
sshConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", containerConfig)
assert.NoError(t, err)
defer sshConn.Close()
ssh.NewClient(sshConn, chans, reqs)
_, err = communication.ContainerIDs(sshConn)
assert.NoError(t, err)
sshSession, err := communication.NewSSHInteraction(sshConn, cfg.ID, feature.MaxPluginVersion-1)
assert.NoError(t, err)
// unblock before grabbing stdout - this should buffer in ssh
sshSession.Unblock()
stdout := sshSession.Stdout()
// FIXME: the pipe pair are line buffered - how do I disable that so we
// don't have odd hangs to diagnose when the trailing \n is missed
testBytes := []byte("one\ntwo\nthree\n")
// after tty translation by sort the above string should result in the following
// - we have echo turned on so we get a repeat of the initial string
// - all \n bytes are translated to \r\n
refBytes := []byte("one\r\ntwo\r\nthree\r\none\r\nthree\r\ntwo\r\n")
// read from session into buffer
buf := &bytes.Buffer{}
done := make(chan bool)
go func() {
io.Copy(buf, stdout)
log.Debug("stdout copy complete")
done <- true
}()
// write something to echo
log.Debug("sending test data")
sshSession.Stdin().Write(testBytes)
log.Debug("sent test data")
// wait for the close to propagate
sshSession.CloseStdin()
<-done
assert.Equal(t, refBytes, buf.Bytes())
}
//
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// TestEcho ensures we get back data without a tty and without any stdin interaction
//
func TestEcho(t *testing.T) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "echo",
Name: "tether_test_executor",
},
Diagnostics: executor.Diagnostics{
DebugLevel: 1,
},
Sessions: map[string]*executor.SessionConfig{
"echo": {
Common: executor.Common{
ID: "echo",
Name: "tether_test_session",
},
Tty: false,
Attach: true,
Active: true,
OpenStdin: true,
RunBlock: true,
Cmd: executor.Cmd{
Path: "/bin/echo",
// reading from stdin
Args: []string{"/bin/echo", "hello"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
containerConfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client from the mocked connection
sshConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", containerConfig)
assert.NoError(t, err)
defer sshConn.Close()
ssh.NewClient(sshConn, chans, reqs)
_, err = communication.ContainerIDs(sshConn)
assert.NoError(t, err)
version, err := communication.ContainerVersion(sshConn)
assert.NoError(t, err)
sshSession, err := communication.NewSSHInteraction(sshConn, cfg.ID, version)
assert.NoError(t, err)
// unblock before grabbing stdout - this should buffer in ssh
sshSession.Unblock()
stdout := sshSession.Stdout()
stderr := sshSession.Stderr()
doneStdout := make(chan bool)
doneStderr := make(chan bool)
// read from session into buffer
bufout := &bytes.Buffer{}
go func() {
io.Copy(bufout, stdout)
log.Debug("stdout copy complete")
doneStdout <- true
}()
// read from session into buffer
buferr := &bytes.Buffer{}
go func() {
io.Copy(buferr, stderr)
log.Debug("stderr copy complete")
doneStderr <- true
}()
// wait for the close to propagate
<-doneStdout
assert.Equal(t, "hello\n", string(bufout.Bytes()))
<-doneStderr
assert.Equal(t, "", string(buferr.Bytes()))
}
func TestEchoRepeat(t *testing.T) {
log.SetLevel(log.WarnLevel)
for i := 0; i < 10 && !t.Failed(); i++ {
TestEcho(t)
}
defer log.SetLevel(log.DebugLevel)
}
//
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// TestAttachMultiple sets up the config for attach testing - tests launching and
// attaching to multiple processes simultaneously
//
func TestAttachMultiple(t *testing.T) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "tee1",
Name: "tether_test_executor",
},
Sessions: map[string]*executor.SessionConfig{
"tee1": {
Common: executor.Common{
ID: "tee1",
Name: "tether_test_session",
},
Tty: false,
Attach: true,
Active: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee1.out"},
Env: []string{},
Dir: "/",
},
},
"tee2": {
Common: executor.Common{
ID: "tee2",
Name: "tether_test_session2",
},
Tty: false,
Attach: true,
Active: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee2.out"},
Env: []string{},
Dir: "/",
},
},
"tee3": {
Common: executor.Common{
ID: "tee3",
Name: "tether_test_session2",
},
Tty: false,
Attach: false,
Active: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee3.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
Diagnostics: executor.Diagnostics{
DebugLevel: 1,
},
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-mocker.Started
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
cconfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client
sConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", cconfig)
assert.NoError(t, err)
defer sConn.Close()
client := ssh.NewClient(sConn, chans, reqs)
ids, err := communication.ContainerIDs(client)
assert.NoError(t, err)
version, err := communication.ContainerVersion(client)
assert.NoError(t, err)
// there's no ordering guarantee in the returned ids
if len(ids) != len(cfg.Sessions) {
t.Errorf("ID list - expected %d, got %d", len(cfg.Sessions), len(ids))
}
// check the ids we got correspond to those in the config
for _, id := range ids {
if _, ok := cfg.Sessions[id]; !ok {
t.Errorf("Expected sessions to have an entry for %s", id)
}
}
sessionA, err := communication.NewSSHInteraction(client, "tee1", version)
assert.NoError(t, err)
sessionB, err := communication.NewSSHInteraction(client, "tee2", version)
assert.NoError(t, err)
stdoutA := sessionA.Stdout()
stdoutB := sessionB.Stdout()
// FIXME: this is line buffered - how do I disable that so we don't have odd hangs to diagnose
// when the trailing \n is missed
testBytesA := []byte("hello world!\n")
testBytesB := []byte("goodbye world!\n")
// read from session into buffer
bufA := &bytes.Buffer{}
bufB := &bytes.Buffer{}
var wg sync.WaitGroup
// wg.Add cannot go inside the go routines as the Add may not have happened by the time we call Wait
wg.Add(2)
go func() {
io.CopyN(bufA, stdoutA, int64(len(testBytesA)))
wg.Done()
}()
go func() {
io.CopyN(bufB, stdoutB, int64(len(testBytesB)))
wg.Done()
}()
// write something to echo
log.Debug("sending test data")
sessionA.Stdin().Write(testBytesA)
sessionB.Stdin().Write(testBytesB)
log.Debug("sent test data")
// wait for the close to propagate
wg.Wait()
sessionA.CloseStdin()
sessionB.CloseStdin()
<-mocker.Cleaned
assert.Equal(t, bufA.Bytes(), testBytesA)
assert.Equal(t, bufB.Bytes(), testBytesB)
}
//
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// TestAttachInvalid sets up the config for attach testing - launches a process but
// tries to attach to an invalid session id
//
func TestAttachInvalid(t *testing.T) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "attachinvalid",
Name: "tether_test_executor",
},
Sessions: map[string]*executor.SessionConfig{
"valid": {
Common: executor.Common{
ID: "valid",
Name: "tether_test_session",
},
Tty: false,
Attach: true,
Active: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
tthr, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
cconfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client
sConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", cconfig)
assert.NoError(t, err)
defer sConn.Close()
client := ssh.NewClient(sConn, chans, reqs)
version, err := communication.ContainerVersion(client)
assert.NoError(t, err)
_, err = communication.NewSSHInteraction(client, "invalid", version)
tthr.Stop()
if err == nil {
t.Errorf("Expected to fail on attempt to attach to invalid session")
}
}
//
/////////////////////////////////////////////////////////////////////////////////////
// Start the tether, start a mock esx serial to tcp connection, start the
// attach server, try to Get() the tether's attached session.
/*
func TestMockAttachTetherToPL(t *testing.T) {
testSetup(t)
defer testTeardown(t)
// Start the PL attach server
testServer := communication.NewAttachServer("", 8080)
assert.NoError(t, testServer.Start())
defer testServer.Stop()
cfg := executor.ExecutorConfig{
Common: executor.Common{
ID: "attach",
Name: "tether_test_executor",
},
Sessions: map[string]*executor.SessionConfig{
"attach": executor.SessionConfig{
Common: executor.Common{
ID: "attach",
Name: "tether_test_session",
},
Tty: true,
Attach: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
StartTether(t, &cfg)
// create a conn on the mock pipe. Reads from pipe, echos to network.
_, err := mockNetworkToSerialConnection(testServer.Addr())
if !assert.NoError(t, err) {
return
}
var pty communication.SessionInteractor
pty, err = testServer.Get(context.Background(), "attach", 600*time.Second)
if !assert.NoError(t, err) {
return
}
err = pty.Resize(1, 2, 3, 4)
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, Mocked.WindowCol, uint32(1)) || !assert.Equal(t, Mocked.WindowRow, uint32(2)) {
return
}
if err = pty.Signal("HUP"); !assert.NoError(t, err) {
return
}
if !assert.Equal(t, Mocked.Signal, ssh.Signal("HUP")) {
return
}
}
*/
func TestReattach(t *testing.T) {
mocker := testSetup(t)
defer testTeardown(t, mocker)
testServer, _ := server.(*testAttachServer)
cfg := executor.ExecutorConfig{
ExecutorConfigCommon: executor.ExecutorConfigCommon{
ID: "attach",
Name: "tether_test_executor",
},
Diagnostics: executor.Diagnostics{
DebugLevel: 1,
},
Sessions: map[string]*executor.SessionConfig{
"attach": {
Common: executor.Common{
ID: "attach",
Name: "tether_test_session",
},
Tty: false,
Attach: true,
Active: true,
RunBlock: true,
OpenStdin: true,
Cmd: executor.Cmd{
Path: "/usr/bin/tee",
// grep, matching everything, reading from stdin
Args: []string{"/usr/bin/tee", pathPrefix + "/tee.out"},
Env: []string{},
Dir: "/",
},
},
},
Key: genKey(),
}
_, _, conn := StartAttachTether(t, &cfg, mocker)
defer conn.Close()
// wait for updates to occur
<-testServer.updated
if !testServer.enabled {
t.Errorf("attach server was not enabled")
}
containerConfig := &ssh.ClientConfig{
User: "daemon",
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// create the SSH client from the mocked connection
sshConn, chans, reqs, err := ssh.NewClientConn(conn, "notappliable", containerConfig)
assert.NoError(t, err)
defer sshConn.Close()
var sshSession communication.SessionInteractor
done := make(chan bool)
buf := &bytes.Buffer{}
testBytes := []byte("\x1b[32mhello world\x1b[39m!\n")
attachFunc := func() {
attachClient := ssh.NewClient(sshConn, chans, reqs)
if attachClient == nil {
t.Errorf("Failed to get ssh.NewClient")
}
_, err = communication.ContainerIDs(sshConn)
assert.NoError(t, err)
version, err := communication.ContainerVersion(sshConn)
assert.NoError(t, err)
sshSession, err = communication.NewSSHInteraction(sshConn, cfg.ID, version)
assert.NoError(t, err)
sshSession.Unblock()
stdout := sshSession.Stdout()
// read from session into buffer
go func() {
io.CopyN(buf, stdout, int64(len(testBytes)))
done <- true
}()
// write something to echo
log.Debug("sending test data")
sshSession.Stdin().Write(testBytes)
log.Debug("sent test data")
}
limit := 10
for i := 0; i <= limit; i++ {
if i > 0 {
// truncate the buffer for the retach
buf.Reset()
testBytes = []byte(fmt.Sprintf("\x1b[32mhello world - again %dth time \x1b[39m!\n", i))
}
// attach
attachFunc()
// wait for the close to propagate
<-done
// send close-stdin if this is the last iteration
if i == limit {
// exit
sshSession.CloseStdin()
} else {
// detach
sshSession.Stdin().Close()
}
assert.Equal(t, buf.Bytes(), testBytes)
}
}