agent: prepare for vm factory

There are a few changes we need on kata agent to introduce vm factory
support:
1. decouple agent creation from sandbox config
2. setup agent without creating a sandbox
3. expose vm storage path and share mount point

Signed-off-by: Peng Tao <bergwolf@gmail.com>
This commit is contained in:
Peng Tao 2018-07-13 16:23:59 +08:00
parent 7f20dd89a3
commit 057214f0fe
10 changed files with 509 additions and 74 deletions

View File

@ -96,20 +96,20 @@ func newAgent(agentType AgentType) agent {
}
// newAgentConfig returns an agent config from a generic SandboxConfig interface.
func newAgentConfig(config SandboxConfig) interface{} {
switch config.AgentType {
func newAgentConfig(agentType AgentType, agentConfig interface{}) interface{} {
switch agentType {
case NoopAgentType:
return nil
case HyperstartAgent:
var hyperConfig HyperConfig
err := mapstructure.Decode(config.AgentConfig, &hyperConfig)
err := mapstructure.Decode(agentConfig, &hyperConfig)
if err != nil {
return err
}
return hyperConfig
case KataContainersAgent:
var kataAgentConfig KataAgentConfig
err := mapstructure.Decode(config.AgentConfig, &kataAgentConfig)
err := mapstructure.Decode(agentConfig, &kataAgentConfig)
if err != nil {
return err
}
@ -209,4 +209,13 @@ type agent interface {
// resumeContainer will resume a paused container
resumeContainer(sandbox *Sandbox, c Container) error
// configure will update agent settings based on provided arguments
configure(h hypervisor, id, sharePath string, builtin bool, config interface{}) error
// getVMPath will return the agent vm socket's directory path
getVMPath(id string) string
// getSharePath will return the agent 9pfs share mount path
getSharePath(id string) string
}

View File

@ -99,7 +99,7 @@ func TestNewAgentFromUnknownAgentType(t *testing.T) {
}
func testNewAgentConfig(t *testing.T, config SandboxConfig, expected interface{}) {
agentConfig := newAgentConfig(config)
agentConfig := newAgentConfig(config.AgentType, config.AgentConfig)
if reflect.DeepEqual(agentConfig, expected) == false {
t.Fatal()
}

View File

@ -85,17 +85,27 @@ const dirMode = os.FileMode(0750) | os.ModeDir
// storagePathSuffix is the suffix used for all storage paths
//
// Note: this very brief path represents "virtcontainers sandboxes". It is as
// Note: this very brief path represents "virtcontainers". It is as
// terse as possible to minimise path length.
const storagePathSuffix = "/vc/sbs"
const storagePathSuffix = "vc"
// sandboxPathSuffix is the suffix used for sandbox storage
const sandboxPathSuffix = "sbs"
// vmPathSuffix is the suffix used for guest VMs.
const vmPathSuffix = "vm"
// configStoragePath is the sandbox configuration directory.
// It will contain one config.json file for each created sandbox.
var configStoragePath = filepath.Join("/var/lib", storagePathSuffix)
var configStoragePath = filepath.Join("/var/lib", storagePathSuffix, sandboxPathSuffix)
// runStoragePath is the sandbox runtime directory.
// It will contain one state.json and one lock file for each created sandbox.
var runStoragePath = filepath.Join("/run", storagePathSuffix)
var runStoragePath = filepath.Join("/run", storagePathSuffix, sandboxPathSuffix)
// RunVMStoragePath is the vm directory.
// It will contain all guest vm sockets and shared mountpoints.
var RunVMStoragePath = filepath.Join("/run", storagePathSuffix, vmPathSuffix)
// resourceStorage is the virtcontainers resources (configuration, state, etc...)
// storage interface.

View File

@ -262,9 +262,17 @@ func (h *hyper) init(sandbox *Sandbox, config interface{}) (err error) {
return nil
}
func (h *hyper) createSandbox(sandbox *Sandbox) (err error) {
func (h *hyper) getVMPath(id string) string {
return filepath.Join(runStoragePath, id)
}
func (h *hyper) getSharePath(id string) string {
return filepath.Join(defaultSharedDir, id)
}
func (h *hyper) configure(hv hypervisor, id, sharePath string, builtin bool, config interface{}) error {
for _, socket := range h.sockets {
err := sandbox.hypervisor.addDevice(socket, serialPortDev)
err := hv.addDevice(socket, serialPortDev)
if err != nil {
return err
}
@ -274,14 +282,18 @@ func (h *hyper) createSandbox(sandbox *Sandbox) (err error) {
// This volume contains all bind mounted container bundles.
sharedVolume := Volume{
MountTag: mountTag,
HostPath: filepath.Join(defaultSharedDir, sandbox.id),
HostPath: sharePath,
}
if err := os.MkdirAll(sharedVolume.HostPath, dirMode); err != nil {
return err
}
return sandbox.hypervisor.addDevice(sharedVolume, fsDev)
return hv.addDevice(sharedVolume, fsDev)
}
func (h *hyper) createSandbox(sandbox *Sandbox) (err error) {
return h.configure(sandbox.hypervisor, "", h.getSharePath(sandbox.id), false, nil)
}
func (h *hyper) capabilities() capabilities {

View File

@ -7,11 +7,13 @@ package virtcontainers
import (
"fmt"
"io/ioutil"
"net"
"reflect"
"testing"
"github.com/kata-containers/runtime/virtcontainers/pkg/hyperstart"
"github.com/stretchr/testify/assert"
"github.com/vishvananda/netlink"
)
@ -156,3 +158,43 @@ func TestProcessHyperRouteDestIPv6Failure(t *testing.T) {
testProcessHyperRoute(t, route, testRouteDeviceName, nil)
}
func TestHyperPathAPI(t *testing.T) {
assert := assert.New(t)
h1 := &hyper{}
h2 := &hyper{}
id := "foobar"
// getVMPath
path1 := h1.getVMPath(id)
path2 := h2.getVMPath(id)
assert.Equal(path1, path2)
// getSharePath
path1 = h1.getSharePath(id)
path2 = h2.getSharePath(id)
assert.Equal(path1, path2)
}
func TestHyperConfigure(t *testing.T) {
assert := assert.New(t)
dir, err := ioutil.TempDir("", "hyperstart-test")
assert.Nil(err)
h := &hyper{}
m := &mockHypervisor{}
c := HyperConfig{}
id := "foobar"
invalidAgent := KataAgentConfig{}
err = h.configure(m, id, dir, true, invalidAgent)
assert.Nil(err)
err = h.configure(m, id, dir, true, c)
assert.Nil(err)
err = h.configure(m, id, dir, false, c)
assert.Nil(err)
}

View File

@ -119,11 +119,19 @@ func parseVSOCKAddr(sock string) (uint32, uint32, error) {
return uint32(cid), uint32(port), nil
}
func (k *kataAgent) generateVMSocket(sandbox *Sandbox, c KataAgentConfig) error {
func (k *kataAgent) getVMPath(id string) string {
return filepath.Join(RunVMStoragePath, id)
}
func (k *kataAgent) getSharePath(id string) string {
return filepath.Join(kataHostSharedDir, id)
}
func (k *kataAgent) generateVMSocket(id string, c KataAgentConfig) error {
cid, port, err := parseVSOCKAddr(c.GRPCSocket)
if err != nil {
// We need to generate a host UNIX socket path for the emulated serial port.
kataSock, err := utils.BuildSocketPath(runStoragePath, sandbox.id, defaultKataSocketName)
kataSock, err := utils.BuildSocketPath(k.getVMPath(id), defaultKataSocketName)
if err != nil {
return err
}
@ -148,7 +156,7 @@ func (k *kataAgent) generateVMSocket(sandbox *Sandbox, c KataAgentConfig) error
func (k *kataAgent) init(sandbox *Sandbox, config interface{}) (err error) {
switch c := config.(type) {
case KataAgentConfig:
if err := k.generateVMSocket(sandbox, c); err != nil {
if err := k.generateVMSocket(sandbox.id, c); err != nil {
return err
}
k.keepConn = c.LongLiveConn
@ -196,10 +204,22 @@ func (k *kataAgent) capabilities() capabilities {
return caps
}
func (k *kataAgent) createSandbox(sandbox *Sandbox) error {
func (k *kataAgent) configure(h hypervisor, id, sharePath string, builtin bool, config interface{}) error {
if config != nil {
switch c := config.(type) {
case KataAgentConfig:
if err := k.generateVMSocket(id, c); err != nil {
return err
}
k.keepConn = c.LongLiveConn
default:
return fmt.Errorf("Invalid config type")
}
}
switch s := k.vmSocket.(type) {
case Socket:
err := sandbox.hypervisor.addDevice(s, serialPortDev)
err := h.addDevice(s, serialPortDev)
if err != nil {
return err
}
@ -209,18 +229,27 @@ func (k *kataAgent) createSandbox(sandbox *Sandbox) error {
return fmt.Errorf("Invalid config type")
}
if builtin {
k.proxyBuiltIn = true
k.state.URL, _ = k.agentURL()
}
// Adding the shared volume.
// This volume contains all bind mounted container bundles.
sharedVolume := Volume{
MountTag: mountGuest9pTag,
HostPath: filepath.Join(kataHostSharedDir, sandbox.id),
HostPath: sharePath,
}
if err := os.MkdirAll(sharedVolume.HostPath, dirMode); err != nil {
return err
}
return sandbox.hypervisor.addDevice(sharedVolume, fsDev)
return h.addDevice(sharedVolume, fsDev)
}
func (k *kataAgent) createSandbox(sandbox *Sandbox) error {
return k.configure(sandbox.hypervisor, sandbox.id, k.getSharePath(sandbox.id), k.proxyBuiltIn, nil)
}
func cmdToKataProcess(cmd Cmd) (process *grpc.Process, err error) {
@ -719,35 +748,15 @@ func (k *kataAgent) rollbackFailingContainerCreation(c *Container) {
}
}
func (k *kataAgent) createContainer(sandbox *Sandbox, c *Container) (p *Process, err error) {
ociSpecJSON, ok := c.config.Annotations[vcAnnotations.ConfigJSONKey]
if !ok {
return nil, errorMissingOCISpec
}
var ctrStorages []*grpc.Storage
var ctrDevices []*grpc.Device
// The rootfs storage volume represents the container rootfs
// mount point inside the guest.
// It can be a block based device (when using block based container
// overlay on the host) mount or a 9pfs one (for all other overlay
// implementations).
rootfs := &grpc.Storage{}
// This is the guest absolute root path for that container.
rootPathParent := filepath.Join(kataGuestSharedDir, c.id)
rootPath := filepath.Join(rootPathParent, rootfsDir)
// In case the container creation fails, the following defer statement
// takes care of rolling back actions previously performed.
defer func() {
if err != nil {
k.rollbackFailingContainerCreation(c)
}
}()
func (k *kataAgent) buildContainerRootfs(sandbox *Sandbox, c *Container, rootPathParent string) (*grpc.Storage, error) {
if c.state.Fstype != "" {
// The rootfs storage volume represents the container rootfs
// mount point inside the guest.
// It can be a block based device (when using block based container
// overlay on the host) mount or a 9pfs one (for all other overlay
// implementations).
rootfs := &grpc.Storage{}
// This is a block based device rootfs.
// Pass a drive name only in case of virtio-blk driver.
@ -773,24 +782,53 @@ func (k *kataAgent) createContainer(sandbox *Sandbox, c *Container) (p *Process,
rootfs.Options = []string{"nouuid"}
}
return rootfs, nil
}
// This is not a block based device rootfs.
// We are going to bind mount it into the 9pfs
// shared drive between the host and the guest.
// With 9pfs we don't need to ask the agent to
// mount the rootfs as the shared directory
// (kataGuestSharedDir) is already mounted in the
// guest. We only need to mount the rootfs from
// the host and it will show up in the guest.
if err := bindMountContainerRootfs(kataHostSharedDir, sandbox.id, c.id, c.rootFs, false); err != nil {
return nil, err
}
return nil, nil
}
func (k *kataAgent) createContainer(sandbox *Sandbox, c *Container) (p *Process, err error) {
ociSpecJSON, ok := c.config.Annotations[vcAnnotations.ConfigJSONKey]
if !ok {
return nil, errorMissingOCISpec
}
var ctrStorages []*grpc.Storage
var ctrDevices []*grpc.Device
var rootfs *grpc.Storage
// This is the guest absolute root path for that container.
rootPathParent := filepath.Join(kataGuestSharedDir, c.id)
rootPath := filepath.Join(rootPathParent, rootfsDir)
// In case the container creation fails, the following defer statement
// takes care of rolling back actions previously performed.
defer func() {
if err != nil {
k.rollbackFailingContainerCreation(c)
}
}()
if rootfs, err = k.buildContainerRootfs(sandbox, c, rootPathParent); err != nil {
return nil, err
} else if rootfs != nil {
// Add rootfs to the list of container storage.
// We only need to do this for block based rootfs, as we
// want the agent to mount it into the right location
// (kataGuestSharedDir/ctrID/
ctrStorages = append(ctrStorages, rootfs)
} else {
// This is not a block based device rootfs.
// We are going to bind mount it into the 9pfs
// shared drive between the host and the guest.
// With 9pfs we don't need to ask the agent to
// mount the rootfs as the shared directory
// (kataGuestSharedDir) is already mounted in the
// guest. We only need to mount the rootfs from
// the host and it will show up in the guest.
if err = bindMountContainerRootfs(kataHostSharedDir, sandbox.id, c.id, c.rootFs, false); err != nil {
return nil, err
}
}
ociSpec := &specs.Spec{}
@ -861,11 +899,12 @@ func (k *kataAgent) createContainer(sandbox *Sandbox, c *Container) (p *Process,
createNSList := []ns.NSType{ns.NSTypePID}
enterNSList := []ns.Namespace{
{
enterNSList := []ns.Namespace{}
if sandbox.networkNS.NetNsPath != "" {
enterNSList = append(enterNSList, ns.Namespace{
Path: sandbox.networkNS.NetNsPath,
Type: ns.NSTypeNet,
},
})
}
return prepareAndStartShim(sandbox, k.shim, c.id, req.ExecId,

View File

@ -6,12 +6,15 @@
package virtcontainers
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"reflect"
"strings"
"syscall"
"testing"
gpb "github.com/gogo/protobuf/types"
@ -25,6 +28,7 @@ import (
"github.com/kata-containers/runtime/virtcontainers/device/api"
"github.com/kata-containers/runtime/virtcontainers/device/config"
"github.com/kata-containers/runtime/virtcontainers/device/drivers"
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
"github.com/kata-containers/runtime/virtcontainers/pkg/mock"
)
@ -243,6 +247,8 @@ var reqList = []interface{}{
}
func TestKataAgentSendReq(t *testing.T) {
assert := assert.New(t)
impl := &gRPCProxy{}
proxy := mock.ProxyGRPCMock{
@ -251,15 +257,12 @@ func TestKataAgentSendReq(t *testing.T) {
}
sockDir, err := testGenerateKataProxySockDir()
if err != nil {
t.Fatal(err)
}
assert.Nil(err)
defer os.RemoveAll(sockDir)
testKataProxyURL := fmt.Sprintf(testKataProxyURLTempl, sockDir)
if err := proxy.Start(testKataProxyURL); err != nil {
t.Fatal(err)
}
err = proxy.Start(testKataProxyURL)
assert.Nil(err)
defer proxy.Stop()
k := &kataAgent{
@ -269,10 +272,58 @@ func TestKataAgentSendReq(t *testing.T) {
}
for _, req := range reqList {
if _, err := k.sendReq(req); err != nil {
t.Fatal(err)
}
_, err = k.sendReq(req)
assert.Nil(err)
}
sandbox := &Sandbox{}
container := &Container{}
execid := "processFooBar"
err = k.startContainer(sandbox, container)
assert.Nil(err)
err = k.signalProcess(container, execid, syscall.SIGKILL, true)
assert.Nil(err)
err = k.winsizeProcess(container, execid, 100, 200)
assert.Nil(err)
_, err = k.processListContainer(sandbox, Container{}, ProcessListOptions{})
assert.Nil(err)
err = k.updateContainer(sandbox, Container{}, specs.LinuxResources{})
assert.Nil(err)
err = k.pauseContainer(sandbox, Container{})
assert.Nil(err)
err = k.resumeContainer(sandbox, Container{})
assert.Nil(err)
err = k.onlineCPUMem(1)
assert.Nil(err)
_, err = k.statsContainer(sandbox, Container{})
assert.Nil(err)
err = k.check()
assert.Nil(err)
_, err = k.waitProcess(container, execid)
assert.Nil(err)
_, err = k.writeProcessStdin(container, execid, []byte{'c'})
assert.Nil(err)
err = k.closeProcessStdin(container, execid)
assert.Nil(err)
_, err = k.readProcessStdout(container, execid, []byte{})
assert.Nil(err)
_, err = k.readProcessStderr(container, execid, []byte{})
assert.Nil(err)
}
func TestGenerateInterfacesAndRoutes(t *testing.T) {
@ -594,3 +645,207 @@ func TestHandlePidNamespace(t *testing.T) {
_, err = k.handlePidNamespace(g, sandbox)
assert.NotNil(err)
}
func TestAgentPathAPI(t *testing.T) {
assert := assert.New(t)
k1 := &kataAgent{}
k2 := &kataAgent{}
id := "foobar"
// getVMPath
path1 := k1.getVMPath(id)
path2 := k2.getVMPath(id)
assert.Equal(path1, path2)
// getSharePath
path1 = k1.getSharePath(id)
path2 = k2.getSharePath(id)
assert.Equal(path1, path2)
// generateVMSocket
c := KataAgentConfig{}
err := k1.generateVMSocket(id, c)
assert.Nil(err)
err = k2.generateVMSocket(id, c)
assert.Nil(err)
assert.Equal(k1, k2)
c.GRPCSocket = "unixsocket"
err = k1.generateVMSocket(id, c)
assert.Nil(err)
_, ok := k1.vmSocket.(Socket)
assert.True(ok)
c.GRPCSocket = "vsock:100:200"
err = k2.generateVMSocket(id, c)
assert.Nil(err)
_, ok = k2.vmSocket.(kataVSOCK)
assert.True(ok)
}
func TestAgentConfigure(t *testing.T) {
assert := assert.New(t)
dir, err := ioutil.TempDir("", "kata-agent-test")
assert.Nil(err)
k := &kataAgent{}
h := &mockHypervisor{}
c := KataAgentConfig{GRPCSocket: "vsock:100:200"}
id := "foobar"
invalidAgent := HyperConfig{}
err = k.configure(h, id, dir, true, invalidAgent)
assert.Error(err)
err = k.configure(h, id, dir, true, c)
assert.Nil(err)
c.GRPCSocket = "foobarfoobar"
err = k.configure(h, id, dir, true, c)
assert.Nil(err)
err = k.configure(h, id, dir, false, c)
assert.Nil(err)
}
func TestParseVSOCKAddr(t *testing.T) {
assert := assert.New(t)
sock := "randomfoobar"
_, _, err := parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock://1:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "unix:1:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:foo:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:1:bar"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:1:2"
cid, port, err := parseVSOCKAddr(sock)
assert.Nil(err)
assert.Equal(cid, uint32(1))
assert.Equal(port, uint32(2))
}
func TestCmdToKataProcess(t *testing.T) {
assert := assert.New(t)
cmd := Cmd{
Args: strings.Split("foo", " "),
Envs: []EnvVar{},
WorkDir: "/",
User: "1000",
PrimaryGroup: "1000",
}
_, err := cmdToKataProcess(cmd)
assert.Nil(err)
cmd1 := cmd
cmd1.User = "foobar"
_, err = cmdToKataProcess(cmd1)
assert.Error(err)
cmd1 = cmd
cmd1.PrimaryGroup = "foobar"
_, err = cmdToKataProcess(cmd1)
assert.Error(err)
cmd1 = cmd
cmd1.User = "foobar:1000"
_, err = cmdToKataProcess(cmd1)
assert.Error(err)
cmd1 = cmd
cmd1.User = "1000:2000"
_, err = cmdToKataProcess(cmd1)
assert.Nil(err)
cmd1 = cmd
cmd1.SupplementaryGroups = []string{"foo"}
_, err = cmdToKataProcess(cmd1)
assert.Error(err)
cmd1 = cmd
cmd1.SupplementaryGroups = []string{"4000"}
_, err = cmdToKataProcess(cmd1)
assert.Nil(err)
}
func TestAgentCreateContainer(t *testing.T) {
assert := assert.New(t)
sandbox := &Sandbox{
id: "foobar",
config: &SandboxConfig{
ID: "foobar",
HypervisorType: MockHypervisor,
HypervisorConfig: HypervisorConfig{
KernelPath: "foo",
ImagePath: "bar",
},
},
hypervisor: &mockHypervisor{},
storage: &filesystem{},
}
container := &Container{
id: "barfoo",
sandboxID: "foobar",
sandbox: sandbox,
state: State{
Fstype: "xfs",
},
config: &ContainerConfig{
Annotations: map[string]string{},
},
}
ociSpec, err := json.Marshal(&specs.Spec{})
assert.Nil(err)
container.config.Annotations[vcAnnotations.ConfigJSONKey] = string(ociSpec[:])
impl := &gRPCProxy{}
proxy := mock.ProxyGRPCMock{
GRPCImplementer: impl,
GRPCRegister: gRPCRegister,
}
sockDir, err := testGenerateKataProxySockDir()
assert.Nil(err)
defer os.RemoveAll(sockDir)
testKataProxyURL := fmt.Sprintf(testKataProxyURLTempl, sockDir)
err = proxy.Start(testKataProxyURL)
assert.Nil(err)
defer proxy.Stop()
k := &kataAgent{
state: KataAgentState{
URL: testKataProxyURL,
},
}
dir, err := ioutil.TempDir("", "kata-agent-test")
assert.Nil(err)
err = k.configure(&mockHypervisor{}, sandbox.id, dir, true, KataAgentConfig{})
assert.Nil(err)
// We'll fail on container metadata file creation, but it helps increasing coverage...
_, err = k.createContainer(sandbox, container)
assert.Error(err)
}

View File

@ -145,3 +145,18 @@ func (n *noopAgent) pauseContainer(sandbox *Sandbox, c Container) error {
func (n *noopAgent) resumeContainer(sandbox *Sandbox, c Container) error {
return nil
}
// configHypervisor is the Noop agent hypervisor configuration implementation. It does nothing.
func (n *noopAgent) configure(h hypervisor, id, sharePath string, builtin bool, config interface{}) error {
return nil
}
// getVMPath is the Noop agent vm path getter. It does nothing.
func (n *noopAgent) getVMPath(id string) string {
return ""
}
// getVMPath is the Noop agent share path getter. It does nothing.
func (n *noopAgent) getSharePath(id string) string {
return ""
}

View File

@ -156,3 +156,56 @@ func TestNoopAgentResumeContainer(t *testing.T) {
t.Fatal(err)
}
}
func TestNoopAgentConfigure(t *testing.T) {
n := &noopAgent{}
h := &mockHypervisor{}
id := "foobar"
sharePath := "foobarDir"
err := n.configure(h, id, sharePath, true, nil)
if err != nil {
t.Fatal(err)
}
}
func TestNoopAgentGetVMPath(t *testing.T) {
n := &noopAgent{}
path := n.getVMPath("")
if path != "" {
t.Fatal("getSharePath returns non empty path")
}
}
func TestNoopAgentGetSharePath(t *testing.T) {
n := &noopAgent{}
path := n.getSharePath("")
if path != "" {
t.Fatal("getSharePath returns non empty path")
}
}
func TestNoopAgentStartProxy(t *testing.T) {
n := &noopAgent{}
sandbox, _, err := testCreateNoopContainer()
if err != nil {
t.Fatal(err)
}
defer cleanUp()
err = n.startProxy(sandbox)
if err != nil {
t.Fatal(err)
}
}
func TestNoopAgentProcessListContainer(t *testing.T) {
n := &noopAgent{}
sandbox, container, err := testCreateNoopContainer()
if err != nil {
t.Fatal(err)
}
defer cleanUp()
_, err = n.processListContainer(sandbox, *container, ProcessListOptions{})
if err != nil {
t.Fatal(err)
}
}

View File

@ -779,7 +779,7 @@ func newSandbox(sandboxConfig SandboxConfig) (*Sandbox, error) {
return nil, err
}
agentConfig := newAgentConfig(sandboxConfig)
agentConfig := newAgentConfig(sandboxConfig.AgentType, sandboxConfig.AgentConfig)
if err = s.agent.init(s, agentConfig); err != nil {
return nil, err
}