Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
channel: support communication channel hotplug
Browse files Browse the repository at this point in the history
Since communication channel (vsock or serial port) is hot plugged by the
runtime, newChannel iterates in a loop looking for the serial port or vsock
device. The timeout is defined by channelExistMaxTries and channelExistWaitTime
and it can be calculated by using the following operation:

```
(channelExistMaxTries * channelExistWaitTime) / 1000 = timeout in seconds
```

If there are neither vsocks nor serial ports, an error is returned.

fixes #298

Signed-off-by: Julio Montes <[email protected]>
  • Loading branch information
Julio Montes committed Aug 8, 2018
1 parent fcfa054 commit f6486e7
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 18 deletions.
2 changes: 1 addition & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// Serial channel
const (
var (
serialChannelName = "agent.channel.0"
virtIOPath = "/sys/class/virtio-ports"
devRootPath = "/dev"
Expand Down
59 changes: 42 additions & 17 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/hashicorp/yamux"
"github.com/mdlayher/vsock"
Expand All @@ -21,35 +22,58 @@ import (
grpcStatus "google.golang.org/grpc/status"
)

var (
channelExistMaxTries = 200
channelExistWaitTime = 50 * time.Millisecond
isAFVSockSupportedFunc = isAFVSockSupported
)

type channel interface {
setup() error
wait() error
listen() (net.Listener, error)
teardown() error
}

// Creates a new channel to communicate the agent with the proxy or shim.
// The runtime hot plugs a serial port or a vsock PCI depending of the configuration
// file and if the host has support for vsocks. newChannel iterates in a loop looking
// for the serial port or vsock device.
// The timeout is defined by channelExistMaxTries and channelExistWaitTime and it
// can be calculated by using the following operation:
// (channelExistMaxTries * channelExistWaitTime) / 1000 = timeout in seconds
// If there are neither vsocks nor serial ports, an error is returned.
func newChannel() (channel, error) {
// Check for vsock support.
vSockSupported, err := isAFVSockSupported()
if err != nil {
return nil, err
}

if vSockSupported {
// Check if vsock socket exists. We want to cover the case
// where the guest OS can support vsock, but the runtime is
// still using virtio serial connection.
exist, err := vSockPathExist()
if err != nil {
return nil, err
var serialErr error
var serialPath string
var vsockErr error
var vSockSupported bool

for i := 0; i < channelExistMaxTries; i++ {
// check vsock path
if _, err := os.Stat(vSockDevPath); err == nil {
if vSockSupported, vsockErr = isAFVSockSupportedFunc(); vSockSupported && vsockErr == nil {
return &vSockChannel{}, nil
}
}

if exist {
return &vSockChannel{}, nil
// Check serial port path
if serialPath, serialErr = findVirtualSerialPath(serialChannelName); serialErr == nil {
return &serialChannel{serialPath: serialPath}, nil
}

time.Sleep(channelExistWaitTime)
}

if serialErr != nil {
agentLog.WithError(serialErr).Error("Serial port not found")
}

if vsockErr != nil {
agentLog.WithError(vsockErr).Error("VSock not found")
}

return &serialChannel{}, nil
return nil, fmt.Errorf("Neither vsocks nor serial ports were found")
}

type vSockChannel struct {
Expand Down Expand Up @@ -77,12 +101,13 @@ func (c *vSockChannel) teardown() error {
}

type serialChannel struct {
serialPath string
serialConn *os.File
}

func (c *serialChannel) setup() error {
// Open serial channel.
file, err := openSerialChannel()
file, err := os.OpenFile(c.serialPath, os.O_RDWR, os.ModeDevice)
if err != nil {
return err
}
Expand Down
53 changes: 53 additions & 0 deletions channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
package main

import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -97,3 +99,54 @@ func TestTeardownSerialChannel(t *testing.T) {
err = c.teardown()
assert.Nil(t, err, "%v", err)
}

func TestNewChannel(t *testing.T) {
assert := assert.New(t)

orgChannelExistMaxTries := channelExistMaxTries
orgChannelExistWaitTime := channelExistWaitTime
orgVSockDevPath := vSockDevPath
orgVirtIOPath := virtIOPath
orgIsAFVSockSupportedFunc := isAFVSockSupportedFunc
channelExistMaxTries = 1
channelExistWaitTime = 0
vSockDevPath = "/abc/xyz/123"
virtIOPath = "/abc/xyz/123"
isAFVSockSupportedFunc = func() (bool, error) { return false, errors.New("vsock") }
defer func() {
channelExistMaxTries = orgChannelExistMaxTries
channelExistWaitTime = orgChannelExistWaitTime
vSockDevPath = orgVSockDevPath
virtIOPath = orgVirtIOPath
isAFVSockSupportedFunc = orgIsAFVSockSupportedFunc
}()

c, err := newChannel()
assert.Error(err)
assert.Nil(c)

vSockDevPath = "/dev/null"
c, err = newChannel()
assert.Error(err)
assert.Nil(c)

isAFVSockSupportedFunc = func() (bool, error) { return true, nil }
c, err = newChannel()
assert.NoError(err)
_, ok := c.(*vSockChannel)
assert.True(ok)

vSockDevPath = "/abc/xyz/123"
virtIOPath, err = ioutil.TempDir("", "virtio")
assert.NoError(err)
portPath := filepath.Join(virtIOPath, "port")
err = os.Mkdir(portPath, 0777)
assert.NoError(err)
defer os.Remove(portPath)
err = ioutil.WriteFile(filepath.Join(portPath, "name"), []byte(serialChannelName), 0777)
assert.NoError(err)
c, err = newChannel()
assert.NoError(err)
_, ok = c.(*serialChannel)
assert.True(ok)
}

0 comments on commit f6486e7

Please sign in to comment.