Merge pull request #1412 from lifupan/shimv2mount

shimv2: optionally plug rootfs block storage instead of mounting it
This commit is contained in:
Peng Tao 2019-04-02 15:30:40 +08:00 committed by GitHub
commit 25d21060e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 322 additions and 129 deletions

View File

@ -127,15 +127,18 @@ func create(ctx context.Context, containerID, bundlePath, console, pidFilePath s
disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal) disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal)
//rootfs has been mounted by containerd shim
rootFs := vc.RootFs{Mounted: true}
var process vc.Process var process vc.Process
switch containerType { switch containerType {
case vc.PodSandbox: case vc.PodSandbox:
_, process, err = katautils.CreateSandbox(ctx, vci, ociSpec, runtimeConfig, containerID, bundlePath, console, disableOutput, systemdCgroup, false) _, process, err = katautils.CreateSandbox(ctx, vci, ociSpec, runtimeConfig, rootFs, containerID, bundlePath, console, disableOutput, systemdCgroup, false)
if err != nil { if err != nil {
return err return err
} }
case vc.PodContainer: case vc.PodContainer:
process, err = katautils.CreateContainer(ctx, vci, nil, ociSpec, containerID, bundlePath, console, disableOutput, false) process, err = katautils.CreateContainer(ctx, vci, nil, ociSpec, rootFs, containerID, bundlePath, console, disableOutput, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -13,29 +13,34 @@ import (
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
vc "github.com/kata-containers/runtime/virtcontainers" vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci" "github.com/kata-containers/runtime/virtcontainers/pkg/oci"
"github.com/pkg/errors"
"os" "os"
"path/filepath"
taskAPI "github.com/containerd/containerd/runtime/v2/task" taskAPI "github.com/containerd/containerd/runtime/v2/task"
"github.com/kata-containers/runtime/pkg/katautils" "github.com/kata-containers/runtime/pkg/katautils"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
containerd_types "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/mount"
"github.com/sirupsen/logrus"
// only register the proto type // only register the proto type
_ "github.com/containerd/containerd/runtime/linux/runctypes" _ "github.com/containerd/containerd/runtime/linux/runctypes"
crioption "github.com/containerd/cri-containerd/pkg/api/runtimeoptions/v1" crioption "github.com/containerd/cri-containerd/pkg/api/runtimeoptions/v1"
) )
func create(ctx context.Context, s *service, r *taskAPI.CreateTaskRequest, netns string) (*container, error) { func create(ctx context.Context, s *service, r *taskAPI.CreateTaskRequest, netns string) (*container, error) {
rootFs := vc.RootFs{Mounted: s.mount}
detach := !r.Terminal if len(r.Rootfs) == 1 {
m := r.Rootfs[0]
// Checks the MUST and MUST NOT from OCI runtime specification rootFs.Source = m.Source
bundlePath, err := validBundle(r.ID, r.Bundle) rootFs.Type = m.Type
if err != nil { rootFs.Options = m.Options
return nil, err
} }
ociSpec, err := oci.ParseConfigJSON(bundlePath) detach := !r.Terminal
ociSpec, bundlePath, err := loadSpec(r, netns)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -45,13 +50,86 @@ func create(ctx context.Context, s *service, r *taskAPI.CreateTaskRequest, netns
return nil, err return nil, err
} }
// Todo: disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal)
// Since there is a bug in kata for sharedPidNs, here to rootfs := filepath.Join(r.Bundle, "rootfs")
// remove the pidns to disable the sharePidNs temporarily,
// once kata fixed this issue, we can remove this line. switch containerType {
// For the bug, please see: case vc.PodSandbox:
// https://github.com/kata-containers/runtime/issues/930 if s.sandbox != nil {
removeNamespace(&ociSpec, specs.PIDNamespace) return nil, fmt.Errorf("cannot create another sandbox in sandbox: %s", s.sandbox.ID())
}
_, err := loadRuntimeConfig(s, r)
if err != nil {
return nil, err
}
defer func() {
if err != nil && s.mount {
if err2 := mount.UnmountAll(rootfs, 0); err2 != nil {
logrus.WithError(err2).Warn("failed to cleanup rootfs mount")
}
}
}()
s.mount = true
if err = checkAndMount(s, r); err != nil {
return nil, err
}
rootFs.Mounted = s.mount
katautils.HandleFactory(ctx, vci, s.config)
sandbox, _, err := katautils.CreateSandbox(ctx, vci, *ociSpec, *s.config, rootFs, r.ID, bundlePath, "", disableOutput, false, true)
if err != nil {
return nil, err
}
s.sandbox = sandbox
case vc.PodContainer:
if s.sandbox == nil {
return nil, fmt.Errorf("BUG: Cannot start the container, since the sandbox hasn't been created")
}
if s.mount {
defer func() {
if err != nil {
if err2 := mount.UnmountAll(rootfs, 0); err2 != nil {
logrus.WithError(err2).Warn("failed to cleanup rootfs mount")
}
}
}()
if err = doMount(r.Rootfs, rootfs); err != nil {
return nil, err
}
}
_, err = katautils.CreateContainer(ctx, vci, s.sandbox, *ociSpec, rootFs, r.ID, bundlePath, "", disableOutput, true)
if err != nil {
return nil, err
}
}
container, err := newContainer(s, r, containerType, ociSpec)
if err != nil {
return nil, err
}
return container, nil
}
func loadSpec(r *taskAPI.CreateTaskRequest, netns string) (*oci.CompatOCISpec, string, error) {
// Checks the MUST and MUST NOT from OCI runtime specification
bundlePath, err := validBundle(r.ID, r.Bundle)
if err != nil {
return nil, "", err
}
ociSpec, err := oci.ParseConfigJSON(bundlePath)
if err != nil {
return nil, "", err
}
//set the network namespace path //set the network namespace path
//this set will be applied to sandbox's //this set will be applied to sandbox's
@ -70,43 +148,15 @@ func create(ctx context.Context, s *service, r *taskAPI.CreateTaskRequest, netns
} }
} }
disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal) // Todo:
// Since there is a bug in kata for sharedPidNs, here to
// remove the pidns to disable the sharePidNs temporarily,
// once kata fixed this issue, we can remove this line.
// For the bug, please see:
// https://github.com/kata-containers/runtime/issues/930
removeNamespace(&ociSpec, specs.PIDNamespace)
switch containerType { return &ociSpec, bundlePath, nil
case vc.PodSandbox:
if s.sandbox != nil {
return nil, fmt.Errorf("cannot create another sandbox in sandbox: %s", s.sandbox.ID())
}
_, err := loadRuntimeConfig(s, r)
if err != nil {
return nil, err
}
katautils.HandleFactory(ctx, vci, s.config)
sandbox, _, err := katautils.CreateSandbox(ctx, vci, ociSpec, *s.config, r.ID, bundlePath, "", disableOutput, false, true)
if err != nil {
return nil, err
}
s.sandbox = sandbox
case vc.PodContainer:
if s.sandbox == nil {
return nil, fmt.Errorf("BUG: Cannot start the container, since the sandbox hasn't been created")
}
_, err = katautils.CreateContainer(ctx, vci, s.sandbox, ociSpec, r.ID, bundlePath, "", disableOutput, true)
if err != nil {
return nil, err
}
}
container, err := newContainer(s, r, containerType, &ociSpec)
if err != nil {
return nil, err
}
return container, nil
} }
func loadRuntimeConfig(s *service, r *taskAPI.CreateTaskRequest) (*oci.RuntimeConfig, error) { func loadRuntimeConfig(s *service, r *taskAPI.CreateTaskRequest) (*oci.RuntimeConfig, error) {
@ -142,3 +192,33 @@ func loadRuntimeConfig(s *service, r *taskAPI.CreateTaskRequest) (*oci.RuntimeCo
return &runtimeConfig, nil return &runtimeConfig, nil
} }
func checkAndMount(s *service, r *taskAPI.CreateTaskRequest) error {
if len(r.Rootfs) == 1 {
m := r.Rootfs[0]
if katautils.IsBlockDevice(m.Source) && !s.config.HypervisorConfig.DisableBlockDeviceUse {
s.mount = false
return nil
}
}
rootfs := filepath.Join(r.Bundle, "rootfs")
if err := doMount(r.Rootfs, rootfs); err != nil {
return err
}
return nil
}
func doMount(mounts []*containerd_types.Mount, rootfs string) error {
for _, rm := range mounts {
m := &mount.Mount{
Type: rm.Type,
Source: rm.Source,
Options: rm.Options,
}
if err := m.Mount(rootfs); err != nil {
return errors.Wrapf(err, "failed to mount rootfs component %v", m)
}
}
return nil
}

View File

@ -39,9 +39,11 @@ func deleteContainer(ctx context.Context, s *service, c *container) error {
return err return err
} }
rootfs := path.Join(c.bundle, "rootfs") if s.mount {
if err := mount.UnmountAll(rootfs, 0); err != nil { rootfs := path.Join(c.bundle, "rootfs")
logrus.WithError(err).Warn("failed to cleanup rootfs mount") if err := mount.UnmountAll(rootfs, 0); err != nil {
logrus.WithError(err).Warn("failed to cleanup rootfs mount")
}
} }
delete(s.containers, c.id) delete(s.containers, c.id)

View File

@ -10,7 +10,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
sysexec "os/exec" sysexec "os/exec"
"path/filepath"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -18,7 +17,6 @@ import (
eventstypes "github.com/containerd/containerd/api/events" eventstypes "github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events" "github.com/containerd/containerd/events"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
cdruntime "github.com/containerd/containerd/runtime" cdruntime "github.com/containerd/containerd/runtime"
cdshim "github.com/containerd/containerd/runtime/v2/shim" cdshim "github.com/containerd/containerd/runtime/v2/shim"
@ -72,6 +70,7 @@ func New(ctx context.Context, id string, publisher events.Publisher) (cdshim.Shi
containers: make(map[string]*container), containers: make(map[string]*container),
events: make(chan interface{}, chSize), events: make(chan interface{}, chSize),
ec: make(chan exit, bufferSize), ec: make(chan exit, bufferSize),
mount: false,
} }
go s.processExits() go s.processExits()
@ -99,6 +98,10 @@ type service struct {
// pid directly. // pid directly.
pid uint32 pid uint32
// if the container's rootfs is block device backed, kata shimv2
// will not do the rootfs mount.
mount bool
context context.Context context context.Context
sandbox vc.VCSandbox sandbox vc.VCSandbox
containers map[string]*container containers map[string]*container
@ -310,39 +313,23 @@ func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (_ *
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
var c *container
var netns string
//the network namespace created by cni plugin //the network namespace created by cni plugin
netns, err := namespaces.NamespaceRequired(ctx) netns, err = namespaces.NamespaceRequired(ctx)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "create namespace") return nil, errors.Wrap(err, "create namespace")
} }
rootfs := filepath.Join(r.Bundle, "rootfs") c, err = create(ctx, s, r, netns)
defer func() {
if err != nil {
if err2 := mount.UnmountAll(rootfs, 0); err2 != nil {
logrus.WithError(err2).Warn("failed to cleanup rootfs mount")
}
}
}()
for _, rm := range r.Rootfs {
m := &mount.Mount{
Type: rm.Type,
Source: rm.Source,
Options: rm.Options,
}
if err := m.Mount(rootfs); err != nil {
return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m)
}
}
container, err := create(ctx, s, r, netns)
if err != nil { if err != nil {
return nil, err return nil, err
} }
container.status = task.StatusCreated c.status = task.StatusCreated
s.containers[r.ID] = container s.containers[r.ID] = c
s.send(&eventstypes.TaskCreate{ s.send(&eventstypes.TaskCreate{
ContainerID: r.ID, ContainerID: r.ID,

View File

@ -169,7 +169,7 @@ func SetEphemeralStorageType(ociSpec oci.CompatOCISpec) oci.CompatOCISpec {
} }
// CreateSandbox create a sandbox container // CreateSandbox create a sandbox container
func CreateSandbox(ctx context.Context, vci vc.VC, ociSpec oci.CompatOCISpec, runtimeConfig oci.RuntimeConfig, func CreateSandbox(ctx context.Context, vci vc.VC, ociSpec oci.CompatOCISpec, runtimeConfig oci.RuntimeConfig, rootFs vc.RootFs,
containerID, bundlePath, console string, disableOutput, systemdCgroup, builtIn bool) (vc.VCSandbox, vc.Process, error) { containerID, bundlePath, console string, disableOutput, systemdCgroup, builtIn bool) (vc.VCSandbox, vc.Process, error) {
span, ctx := Trace(ctx, "createSandbox") span, ctx := Trace(ctx, "createSandbox")
defer span.Finish() defer span.Finish()
@ -183,6 +183,17 @@ func CreateSandbox(ctx context.Context, vci vc.VC, ociSpec oci.CompatOCISpec, ru
sandboxConfig.Stateful = true sandboxConfig.Stateful = true
} }
if !rootFs.Mounted && len(sandboxConfig.Containers) == 1 {
if rootFs.Source != "" {
realPath, err := ResolvePath(rootFs.Source)
if err != nil {
return nil, vc.Process{}, err
}
rootFs.Source = realPath
}
sandboxConfig.Containers[0].RootFs = rootFs
}
// Important to create the network namespace before the sandbox is // Important to create the network namespace before the sandbox is
// created, because it is not responsible for the creation of the // created, because it is not responsible for the creation of the
// netns if it does not exist. // netns if it does not exist.
@ -223,7 +234,7 @@ func CreateSandbox(ctx context.Context, vci vc.VC, ociSpec oci.CompatOCISpec, ru
} }
// CreateContainer create a container // CreateContainer create a container
func CreateContainer(ctx context.Context, vci vc.VC, sandbox vc.VCSandbox, ociSpec oci.CompatOCISpec, containerID, bundlePath, console string, disableOutput, builtIn bool) (vc.Process, error) { func CreateContainer(ctx context.Context, vci vc.VC, sandbox vc.VCSandbox, ociSpec oci.CompatOCISpec, rootFs vc.RootFs, containerID, bundlePath, console string, disableOutput, builtIn bool) (vc.Process, error) {
var c vc.VCContainer var c vc.VCContainer
span, ctx := Trace(ctx, "createContainer") span, ctx := Trace(ctx, "createContainer")
@ -236,6 +247,17 @@ func CreateContainer(ctx context.Context, vci vc.VC, sandbox vc.VCSandbox, ociSp
return vc.Process{}, err return vc.Process{}, err
} }
if !rootFs.Mounted {
if rootFs.Source != "" {
realPath, err := ResolvePath(rootFs.Source)
if err != nil {
return vc.Process{}, err
}
rootFs.Source = realPath
}
contConfig.RootFs = rootFs
}
sandboxID, err := ociSpec.SandboxID() sandboxID, err := ociSpec.SandboxID()
if err != nil { if err != nil {
return vc.Process{}, err return vc.Process{}, err

View File

@ -306,7 +306,9 @@ func TestCreateSandboxConfigFail(t *testing.T) {
Quota: &quota, Quota: &quota,
} }
_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, testContainerID, bundlePath, testConsole, true, true, false) rootFs := vc.RootFs{Mounted: true}
_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, rootFs, testContainerID, bundlePath, testConsole, true, true, false)
assert.Error(err) assert.Error(err)
} }
@ -340,7 +342,9 @@ func TestCreateSandboxFail(t *testing.T) {
spec, err := readOCIConfigFile(ociConfigFile) spec, err := readOCIConfigFile(ociConfigFile)
assert.NoError(err) assert.NoError(err)
_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, testContainerID, bundlePath, testConsole, true, true, false) rootFs := vc.RootFs{Mounted: true}
_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, rootFs, testContainerID, bundlePath, testConsole, true, true, false)
assert.Error(err) assert.Error(err)
assert.True(vcmock.IsMockError(err)) assert.True(vcmock.IsMockError(err))
} }
@ -377,8 +381,10 @@ func TestCreateContainerContainerConfigFail(t *testing.T) {
err = writeOCIConfigFile(spec, ociConfigFile) err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err) assert.NoError(err)
rootFs := vc.RootFs{Mounted: true}
for _, disableOutput := range []bool{true, false} { for _, disableOutput := range []bool{true, false} {
_, err = CreateContainer(context.Background(), testingImpl, nil, spec, testContainerID, bundlePath, testConsole, disableOutput, false) _, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
assert.Error(err) assert.Error(err)
assert.False(vcmock.IsMockError(err)) assert.False(vcmock.IsMockError(err))
assert.True(strings.Contains(err.Error(), containerType)) assert.True(strings.Contains(err.Error(), containerType))
@ -418,8 +424,10 @@ func TestCreateContainerFail(t *testing.T) {
err = writeOCIConfigFile(spec, ociConfigFile) err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err) assert.NoError(err)
rootFs := vc.RootFs{Mounted: true}
for _, disableOutput := range []bool{true, false} { for _, disableOutput := range []bool{true, false} {
_, err = CreateContainer(context.Background(), testingImpl, nil, spec, testContainerID, bundlePath, testConsole, disableOutput, false) _, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
assert.Error(err) assert.Error(err)
assert.True(vcmock.IsMockError(err)) assert.True(vcmock.IsMockError(err))
os.RemoveAll(path) os.RemoveAll(path)
@ -466,8 +474,10 @@ func TestCreateContainer(t *testing.T) {
err = writeOCIConfigFile(spec, ociConfigFile) err = writeOCIConfigFile(spec, ociConfigFile)
assert.NoError(err) assert.NoError(err)
rootFs := vc.RootFs{Mounted: true}
for _, disableOutput := range []bool{true, false} { for _, disableOutput := range []bool{true, false} {
_, err = CreateContainer(context.Background(), testingImpl, nil, spec, testContainerID, bundlePath, testConsole, disableOutput, false) _, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
assert.NoError(err) assert.NoError(err)
os.RemoveAll(path) os.RemoveAll(path)
} }

View File

@ -8,6 +8,7 @@ package katautils
import ( import (
"fmt" "fmt"
"golang.org/x/sys/unix"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@ -78,6 +79,29 @@ func ResolvePath(path string) (string, error) {
return resolved, nil return resolved, nil
} }
// IsBlockDevice returns true if the give path is a block device
func IsBlockDevice(filePath string) bool {
var stat unix.Stat_t
if filePath == "" {
return false
}
devicePath, err := ResolvePath(filePath)
if err != nil {
return false
}
if err := unix.Stat(devicePath, &stat); err != nil {
return false
}
if stat.Mode&unix.S_IFBLK == unix.S_IFBLK {
return true
}
return false
}
// fileSize returns the number of bytes in the specified file // fileSize returns the number of bytes in the specified file
func fileSize(file string) (int64, error) { func fileSize(file string) (int64, error) {
st := syscall.Stat_t{} st := syscall.Stat_t{}

View File

@ -589,7 +589,7 @@ func statusContainer(sandbox *Sandbox, containerID string) (ContainerStatus, err
State: container.state, State: container.state,
PID: container.process.Pid, PID: container.process.Pid,
StartTime: container.process.StartTime, StartTime: container.process.StartTime,
RootFs: container.config.RootFs, RootFs: container.config.RootFs.Target,
Annotations: container.config.Annotations, Annotations: container.config.Annotations,
}, nil }, nil
} }

View File

@ -65,7 +65,7 @@ func newTestSandboxConfigNoop() SandboxConfig {
// Define the container command and bundle. // Define the container command and bundle.
container := ContainerConfig{ container := ContainerConfig{
ID: containerID, ID: containerID,
RootFs: filepath.Join(testDir, testBundle), RootFs: RootFs{Target: filepath.Join(testDir, testBundle), Mounted: true},
Cmd: newBasicTestCmd(), Cmd: newBasicTestCmd(),
Annotations: containerAnnotations, Annotations: containerAnnotations,
} }
@ -98,7 +98,7 @@ func newTestSandboxConfigHyperstartAgent() SandboxConfig {
// Define the container command and bundle. // Define the container command and bundle.
container := ContainerConfig{ container := ContainerConfig{
ID: containerID, ID: containerID,
RootFs: filepath.Join(testDir, testBundle), RootFs: RootFs{Target: filepath.Join(testDir, testBundle), Mounted: true},
Cmd: newBasicTestCmd(), Cmd: newBasicTestCmd(),
Annotations: containerAnnotations, Annotations: containerAnnotations,
} }
@ -136,7 +136,7 @@ func newTestSandboxConfigHyperstartAgentDefaultNetwork() SandboxConfig {
// Define the container command and bundle. // Define the container command and bundle.
container := ContainerConfig{ container := ContainerConfig{
ID: containerID, ID: containerID,
RootFs: filepath.Join(testDir, testBundle), RootFs: RootFs{Target: filepath.Join(testDir, testBundle), Mounted: true},
Cmd: newBasicTestCmd(), Cmd: newBasicTestCmd(),
Annotations: containerAnnotations, Annotations: containerAnnotations,
} }
@ -1026,7 +1026,7 @@ func newTestContainerConfigNoop(contID string) ContainerConfig {
// Define the container command and bundle. // Define the container command and bundle.
container := ContainerConfig{ container := ContainerConfig{
ID: contID, ID: contID,
RootFs: filepath.Join(testDir, testBundle), RootFs: RootFs{Target: filepath.Join(testDir, testBundle), Mounted: true},
Cmd: newBasicTestCmd(), Cmd: newBasicTestCmd(),
Annotations: containerAnnotations, Annotations: containerAnnotations,
} }
@ -2053,7 +2053,7 @@ func createNewContainerConfigs(numOfContainers int) []ContainerConfig {
return nil return nil
} }
rootFs := filepath.Dir(thisFile) + "/utils/supportfiles/bundles/busybox/" rootFs := RootFs{Target: filepath.Dir(thisFile) + "/utils/supportfiles/bundles/busybox/", Mounted: true}
for i := 0; i < numOfContainers; i++ { for i := 0; i < numOfContainers; i++ {
contConfig := ContainerConfig{ contConfig := ContainerConfig{

View File

@ -208,7 +208,7 @@ type ContainerConfig struct {
ID string ID string
// RootFs is the container workload image on the host. // RootFs is the container workload image on the host.
RootFs string RootFs RootFs
// ReadOnlyRootfs indicates if the rootfs should be mounted readonly // ReadOnlyRootfs indicates if the rootfs should be mounted readonly
ReadonlyRootfs bool ReadonlyRootfs bool
@ -261,13 +261,27 @@ type ContainerDevice struct {
ContainerPath string ContainerPath string
} }
// RootFs describes the container's rootfs.
type RootFs struct {
// Source specifies the BlockDevice path
Source string
// Target specify where the rootfs is mounted if it has been mounted
Target string
// Type specifies the type of filesystem to mount.
Type string
// Options specifies zero or more fstab style mount options.
Options []string
// Mounted specifies whether the rootfs has be mounted or not
Mounted bool
}
// Container is composed of a set of containers and a runtime environment. // Container is composed of a set of containers and a runtime environment.
// A Container can be created, deleted, started, stopped, listed, entered, paused and restored. // A Container can be created, deleted, started, stopped, listed, entered, paused and restored.
type Container struct { type Container struct {
id string id string
sandboxID string sandboxID string
rootFs string rootFs RootFs
config *ContainerConfig config *ContainerConfig
@ -1108,7 +1122,17 @@ func (c *Container) resume() error {
} }
func (c *Container) hotplugDrive() error { func (c *Container) hotplugDrive() error {
dev, err := getDeviceForPath(c.rootFs) var dev device
var err error
// container rootfs is blockdevice backed and isn't mounted
if !c.rootFs.Mounted {
dev, err = getDeviceForPath(c.rootFs.Source)
// there is no "rootfs" dir on block device backed rootfs
c.rootfsSuffix = ""
} else {
dev, err = getDeviceForPath(c.rootFs.Target)
}
if err == errMountPointNotFound { if err == errMountPointNotFound {
return nil return nil
@ -1133,14 +1157,17 @@ func (c *Container) hotplugDrive() error {
return nil return nil
} }
if dev.mountPoint == c.rootFs { devicePath := c.rootFs.Source
c.rootfsSuffix = "" fsType := c.rootFs.Type
} if c.rootFs.Mounted {
if dev.mountPoint == c.rootFs.Target {
// If device mapper device, then fetch the full path of the device c.rootfsSuffix = ""
devicePath, fsType, err := GetDevicePathAndFsType(dev.mountPoint) }
if err != nil { // If device mapper device, then fetch the full path of the device
return err devicePath, fsType, err = GetDevicePathAndFsType(dev.mountPoint)
if err != nil {
return err
}
} }
devicePath, err = filepath.EvalSymlinks(devicePath) devicePath, err = filepath.EvalSymlinks(devicePath)
@ -1153,6 +1180,14 @@ func (c *Container) hotplugDrive() error {
"fs-type": fsType, "fs-type": fsType,
}).Info("Block device detected") }).Info("Block device detected")
if err = c.plugDevice(devicePath); err != nil {
return err
}
return c.setStateFstype(fsType)
}
func (c *Container) plugDevice(devicePath string) error {
var stat unix.Stat_t var stat unix.Stat_t
if err := unix.Stat(devicePath, &stat); err != nil { if err := unix.Stat(devicePath, &stat); err != nil {
return fmt.Errorf("stat %q failed: %v", devicePath, err) return fmt.Errorf("stat %q failed: %v", devicePath, err)
@ -1181,8 +1216,7 @@ func (c *Container) hotplugDrive() error {
return err return err
} }
} }
return nil
return c.setStateFstype(fsType)
} }
// isDriveUsed checks if a drive has been used for container rootfs // isDriveUsed checks if a drive has been used for container rootfs

View File

@ -236,7 +236,7 @@ func TestContainerAddDriveDir(t *testing.T) {
container := Container{ container := Container{
sandbox: sandbox, sandbox: sandbox,
id: contID, id: contID,
rootFs: fakeRootfs, rootFs: RootFs{Target: fakeRootfs, Mounted: true},
} }
containerStore, err := store.NewVCContainerStore(sandbox.ctx, sandbox.id, container.id) containerStore, err := store.NewVCContainerStore(sandbox.ctx, sandbox.id, container.id)
@ -306,7 +306,7 @@ func TestContainerRootfsPath(t *testing.T) {
container := Container{ container := Container{
id: "rootfstestcontainerid", id: "rootfstestcontainerid",
sandbox: sandbox, sandbox: sandbox,
rootFs: fakeRootfs, rootFs: RootFs{Target: fakeRootfs, Mounted: true},
rootfsSuffix: "rootfs", rootfsSuffix: "rootfs",
} }
cvcstore, err := store.NewVCContainerStore(context.Background(), cvcstore, err := store.NewVCContainerStore(context.Background(),
@ -319,7 +319,7 @@ func TestContainerRootfsPath(t *testing.T) {
assert.Empty(t, container.rootfsSuffix) assert.Empty(t, container.rootfsSuffix)
// Reset the value to test the other case // Reset the value to test the other case
container.rootFs = fakeRootfs + "/rootfs" container.rootFs = RootFs{Target: fakeRootfs + "/rootfs", Mounted: true}
container.rootfsSuffix = "rootfs" container.rootfsSuffix = "rootfs"
container.hotplugDrive() container.hotplugDrive()

View File

@ -14,7 +14,7 @@ import (
"github.com/kata-containers/runtime/virtcontainers/types" "github.com/kata-containers/runtime/virtcontainers/types"
) )
const containerRootfs = "/var/lib/container/bundle/" var containerRootfs = vc.RootFs{Target: "/var/lib/container/bundle/", Mounted: true}
// This example creates and starts a single container sandbox, // This example creates and starts a single container sandbox,
// using qemu as the hypervisor and hyperstart as the VM agent. // using qemu as the hypervisor and hyperstart as the VM agent.

View File

@ -594,7 +594,7 @@ func createContainer(context *cli.Context) error {
containerConfig := vc.ContainerConfig{ containerConfig := vc.ContainerConfig{
ID: id, ID: id,
RootFs: context.String("rootfs"), RootFs: vc.RootFs{Target: context.String("rootfs"), Mounted: true},
Cmd: cmd, Cmd: cmd,
} }

View File

@ -546,7 +546,7 @@ func (h *hyper) startOneContainer(sandbox *Sandbox, c *Container) error {
container.Fstype = c.state.Fstype container.Fstype = c.state.Fstype
} else { } else {
if err := bindMountContainerRootfs(c.ctx, defaultSharedDir, sandbox.id, c.id, c.rootFs, false); err != nil { if err := bindMountContainerRootfs(c.ctx, defaultSharedDir, sandbox.id, c.id, c.rootFs.Target, false); err != nil {
bindUnmountAllRootfs(c.ctx, defaultSharedDir, sandbox) bindUnmountAllRootfs(c.ctx, defaultSharedDir, sandbox)
return err return err
} }

View File

@ -982,7 +982,7 @@ func (k *kataAgent) buildContainerRootfs(sandbox *Sandbox, c *Container, rootPat
// (kataGuestSharedDir) is already mounted in the // (kataGuestSharedDir) is already mounted in the
// guest. We only need to mount the rootfs from // guest. We only need to mount the rootfs from
// the host and it will show up in the guest. // the host and it will show up in the guest.
if err := bindMountContainerRootfs(k.ctx, kataHostSharedDir, sandbox.id, c.id, c.rootFs, false); err != nil { if err := bindMountContainerRootfs(k.ctx, kataHostSharedDir, sandbox.id, c.id, c.rootFs.Target, false); err != nil {
return nil, err return nil, err
} }

View File

@ -86,10 +86,21 @@ var errMountPointNotFound = errors.New("Mount point not found")
// //
// device { // device {
// major : major(/dev/sda1) // major : major(/dev/sda1)
// manor : minor(/dev/sda1) // minor : minor(/dev/sda1)
// mountPoint: /a/b/c // mountPoint: /a/b/c
// } // }
//
// if the path is a device path file such as /dev/sda1, it would return
//
// device {
// major : major(/dev/sda1)
// minor : minor(/dev/sda1)
// mountPoint:
func getDeviceForPath(path string) (device, error) { func getDeviceForPath(path string) (device, error) {
var devMajor int
var devMinor int
if path == "" { if path == "" {
return device{}, fmt.Errorf("Path cannot be empty") return device{}, fmt.Errorf("Path cannot be empty")
} }
@ -100,9 +111,20 @@ func getDeviceForPath(path string) (device, error) {
return device{}, err return device{}, err
} }
if isHostDevice(path) {
// stat.Rdev describes the device that this file (inode) represents.
devMajor = major(stat.Rdev)
devMinor = minor(stat.Rdev)
return device{
major: devMajor,
minor: devMinor,
mountPoint: "",
}, nil
}
// stat.Dev points to the underlying device containing the file // stat.Dev points to the underlying device containing the file
major := major(stat.Dev) devMajor = major(stat.Dev)
minor := minor(stat.Dev) devMinor = minor(stat.Dev)
path, err = filepath.Abs(path) path, err = filepath.Abs(path)
if err != nil { if err != nil {
@ -113,8 +135,8 @@ func getDeviceForPath(path string) (device, error) {
if path == "/" { if path == "/" {
return device{ return device{
major: major, major: devMajor,
minor: minor, minor: devMinor,
mountPoint: mountPoint, mountPoint: mountPoint,
}, nil }, nil
} }
@ -144,8 +166,8 @@ func getDeviceForPath(path string) (device, error) {
} }
dev := device{ dev := device{
major: major, major: devMajor,
minor: minor, minor: devMinor,
mountPoint: mountPoint, mountPoint: mountPoint,
} }

View File

@ -516,17 +516,17 @@ func SandboxConfig(ocispec CompatOCISpec, runtime RuntimeConfig, bundlePath, cid
// ContainerConfig converts an OCI compatible runtime configuration // ContainerConfig converts an OCI compatible runtime configuration
// file to a virtcontainers container configuration structure. // file to a virtcontainers container configuration structure.
func ContainerConfig(ocispec CompatOCISpec, bundlePath, cid, console string, detach bool) (vc.ContainerConfig, error) { func ContainerConfig(ocispec CompatOCISpec, bundlePath, cid, console string, detach bool) (vc.ContainerConfig, error) {
ociSpecJSON, err := json.Marshal(ocispec) ociSpecJSON, err := json.Marshal(ocispec)
if err != nil { if err != nil {
return vc.ContainerConfig{}, err return vc.ContainerConfig{}, err
} }
rootfs := ocispec.Root.Path rootfs := vc.RootFs{Target: ocispec.Root.Path, Mounted: true}
if !filepath.IsAbs(rootfs) { if !filepath.IsAbs(rootfs.Target) {
rootfs = filepath.Join(bundlePath, ocispec.Root.Path) rootfs.Target = filepath.Join(bundlePath, ocispec.Root.Path)
} }
ociLog.Debugf("container rootfs: %s", rootfs)
ociLog.Debugf("container rootfs: %s", rootfs.Target)
cmd := types.Cmd{ cmd := types.Cmd{
Args: ocispec.Process.Args, Args: ocispec.Process.Args,

View File

@ -200,7 +200,7 @@ func TestMinimalSandboxConfig(t *testing.T) {
expectedContainerConfig := vc.ContainerConfig{ expectedContainerConfig := vc.ContainerConfig{
ID: containerID, ID: containerID,
RootFs: path.Join(tempBundlePath, "rootfs"), RootFs: vc.RootFs{Target: path.Join(tempBundlePath, "rootfs"), Mounted: true},
ReadonlyRootfs: true, ReadonlyRootfs: true,
Cmd: expectedCmd, Cmd: expectedCmd,
Annotations: map[string]string{ Annotations: map[string]string{

View File

@ -284,12 +284,17 @@ func (s *Sandbox) releaseStatelessSandbox() error {
func (s *Sandbox) Status() SandboxStatus { func (s *Sandbox) Status() SandboxStatus {
var contStatusList []ContainerStatus var contStatusList []ContainerStatus
for _, c := range s.containers { for _, c := range s.containers {
rootfs := c.config.RootFs.Source
if c.config.RootFs.Mounted {
rootfs = c.config.RootFs.Target
}
contStatusList = append(contStatusList, ContainerStatus{ contStatusList = append(contStatusList, ContainerStatus{
ID: c.id, ID: c.id,
State: c.state, State: c.state,
PID: c.process.Pid, PID: c.process.Pid,
StartTime: c.process.StartTime, StartTime: c.process.StartTime,
RootFs: c.config.RootFs, RootFs: rootfs,
Annotations: c.config.Annotations, Annotations: c.config.Annotations,
}) })
} }
@ -1170,13 +1175,17 @@ func (s *Sandbox) StatusContainer(containerID string) (ContainerStatus, error) {
} }
for id, c := range s.containers { for id, c := range s.containers {
rootfs := c.config.RootFs.Source
if c.config.RootFs.Mounted {
rootfs = c.config.RootFs.Target
}
if id == containerID { if id == containerID {
return ContainerStatus{ return ContainerStatus{
ID: c.id, ID: c.id,
State: c.state, State: c.state,
PID: c.process.Pid, PID: c.process.Pid,
StartTime: c.process.StartTime, StartTime: c.process.StartTime,
RootFs: c.config.RootFs, RootFs: rootfs,
Annotations: c.config.Annotations, Annotations: c.config.Annotations,
}, nil }, nil
} }