Add container creation logic to Libpod
Signed-off-by: Matthew Heon <mheon@redhat.com>
This commit is contained in:
parent
8d78e3cfac
commit
241653e152
8 changed files with 873 additions and 51 deletions
|
@ -1,52 +1,121 @@
|
||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/ulule/deepcopier"
|
"github.com/ulule/deepcopier"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ContainerState represents the current state of a container
|
||||||
|
type ContainerState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ContainerStateUnknown indicates that the container is in an error
|
||||||
|
// state where information about it cannot be retrieved
|
||||||
|
ContainerStateUnknown ContainerState = iota
|
||||||
|
// ContainerStateConfigured indicates that the container has had its
|
||||||
|
// storage configured but it has not been created in the OCI runtime
|
||||||
|
ContainerStateConfigured ContainerState = iota
|
||||||
|
// ContainerStateCreated indicates the container has been created in
|
||||||
|
// the OCI runtime but not started
|
||||||
|
ContainerStateCreated ContainerState = iota
|
||||||
|
// ContainerStateRunning indicates the container is currently executing
|
||||||
|
ContainerStateRunning ContainerState = iota
|
||||||
|
// ContainerStateStopped indicates that the container was running but has
|
||||||
|
// exited
|
||||||
|
ContainerStateStopped ContainerState = iota
|
||||||
|
// ContainerStatePaused indicates that the container has been paused
|
||||||
|
ContainerStatePaused ContainerState = iota
|
||||||
|
)
|
||||||
|
|
||||||
// Container is a single OCI container
|
// Container is a single OCI container
|
||||||
type Container struct {
|
type Container struct {
|
||||||
id string
|
config *containerConfig
|
||||||
name string
|
|
||||||
|
|
||||||
spec *spec.Spec
|
pod *Pod
|
||||||
pod *Pod
|
runningSpec *spec.Spec
|
||||||
|
state ContainerState
|
||||||
|
|
||||||
valid bool
|
// The location of the on-disk OCI runtime spec
|
||||||
lock sync.RWMutex
|
specfilePath string
|
||||||
|
// Path to the nonvolatile container configuration file
|
||||||
|
statefilePath string
|
||||||
|
// Path to the container's non-volatile directory
|
||||||
|
containerDir string
|
||||||
|
// Path to the container's volatile directory
|
||||||
|
containerRunDir string
|
||||||
|
// Path to the mountpoint of the container's root filesystem
|
||||||
|
containerMountPoint string
|
||||||
|
|
||||||
|
// Whether this container was configured with containers/storage
|
||||||
|
useContainerStorage bool
|
||||||
|
// Containers/storage information on container
|
||||||
|
// Will be empty if container is configured using a directory
|
||||||
|
containerStorageInfo *ContainerInfo
|
||||||
|
|
||||||
|
// TODO move to storage.Locker from sync.Mutex
|
||||||
|
valid bool
|
||||||
|
lock sync.Mutex
|
||||||
|
runtime *Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
// containerConfig contains all information that was used to create the
|
||||||
|
// container. It may not be changed once created.
|
||||||
|
// It is stored as an unchanging part of on-disk state
|
||||||
|
type containerConfig struct {
|
||||||
|
Spec *spec.Spec `json:"spec"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RootfsDir *string `json:"rootfsDir,omitempty"`
|
||||||
|
RootfsImageID *string `json:"rootfsImageID,omitempty"`
|
||||||
|
RootfsImageName *string `json:"rootfsImageName,omitempty"`
|
||||||
|
UseImageConfig bool `json:"useImageConfig"`
|
||||||
|
Pod *string `json:"pod,omitempty"`
|
||||||
|
SharedNamespaceCtr *string `json:"shareNamespacesWith,omitempty"`
|
||||||
|
SharedNamespaceMap map[string]string `json:"sharedNamespaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the container's ID
|
// ID returns the container's ID
|
||||||
func (c *Container) ID() string {
|
func (c *Container) ID() string {
|
||||||
// No locking needed, ID will never mutate after a container is created
|
return c.config.ID
|
||||||
return c.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the container's name
|
// Name returns the container's name
|
||||||
func (c *Container) Name() string {
|
func (c *Container) Name() string {
|
||||||
return c.name
|
return c.config.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spec returns the container's OCI runtime spec
|
// Spec returns the container's OCI runtime spec
|
||||||
|
// The spec returned is the one used to create the container. The running
|
||||||
|
// spec may differ slightly as mounts are added based on the image
|
||||||
func (c *Container) Spec() *spec.Spec {
|
func (c *Container) Spec() *spec.Spec {
|
||||||
// The spec can potentially be altered when storage is configured and to
|
|
||||||
// add annotations at container create time
|
|
||||||
// As such, access to it is locked
|
|
||||||
c.lock.RLock()
|
|
||||||
defer c.lock.RUnlock()
|
|
||||||
|
|
||||||
spec := new(spec.Spec)
|
spec := new(spec.Spec)
|
||||||
deepcopier.Copy(c.spec).To(spec)
|
deepcopier.Copy(c.config.Spec).To(spec)
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State returns the current state of the container
|
||||||
|
func (c *Container) State() (ContainerState, error) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
// TODO uncomment when working
|
||||||
|
// if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil {
|
||||||
|
// return ContainerStateUnknown, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
return c.state, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Make a new container
|
// Make a new container
|
||||||
func newContainer(rspec *spec.Spec) (*Container, error) {
|
func newContainer(rspec *spec.Spec) (*Container, error) {
|
||||||
if rspec == nil {
|
if rspec == nil {
|
||||||
|
@ -54,23 +123,147 @@ func newContainer(rspec *spec.Spec) (*Container, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctr := new(Container)
|
ctr := new(Container)
|
||||||
ctr.id = stringid.GenerateNonCryptoID()
|
ctr.config = new(containerConfig)
|
||||||
ctr.name = ctr.id // TODO generate unique human-readable names
|
|
||||||
|
|
||||||
ctr.spec = new(spec.Spec)
|
ctr.config.ID = stringid.GenerateNonCryptoID()
|
||||||
deepcopier.Copy(rspec).To(ctr.spec)
|
ctr.config.Name = ctr.config.ID // TODO generate unique human-readable names
|
||||||
|
|
||||||
|
ctr.config.Spec = new(spec.Spec)
|
||||||
|
deepcopier.Copy(rspec).To(ctr.config.Spec)
|
||||||
|
|
||||||
return ctr, nil
|
return ctr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create container root filesystem for use
|
||||||
|
func (c *Container) setupStorage() error {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if !c.valid {
|
||||||
|
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.state != ContainerStateConfigured {
|
||||||
|
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Configured state to have storage set up", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're configured to use a directory, perform that setup
|
||||||
|
if c.config.RootfsDir != nil {
|
||||||
|
// TODO implement directory-based root filesystems
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not using a directory, so call into containers/storage
|
||||||
|
return c.setupImageRootfs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up an image as root filesystem using containers/storage
|
||||||
|
func (c *Container) setupImageRootfs() error {
|
||||||
|
// Need both an image ID and image name, plus a bool telling us whether to use the image configuration
|
||||||
|
if c.config.RootfsImageID == nil || c.config.RootfsImageName == nil {
|
||||||
|
return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO SELinux mount label
|
||||||
|
containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, *c.config.RootfsImageName, *c.config.RootfsImageID, c.config.Name, c.config.ID, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating container storage")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.useContainerStorage = true
|
||||||
|
c.containerStorageInfo = &containerInfo
|
||||||
|
c.containerDir = containerInfo.Dir
|
||||||
|
c.containerRunDir = containerInfo.RunDir
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates a container in the OCI runtime
|
// Create creates a container in the OCI runtime
|
||||||
func (c *Container) Create() error {
|
func (c *Container) Create() (err error) {
|
||||||
return ErrNotImplemented
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if !c.valid {
|
||||||
|
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.state != ContainerStateConfigured {
|
||||||
|
return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If using containers/storage, mount the container
|
||||||
|
if !c.useContainerStorage {
|
||||||
|
// TODO implemented directory-based root filesystems
|
||||||
|
return ErrNotImplemented
|
||||||
|
} else {
|
||||||
|
mountPoint, err := c.runtime.storageService.StartContainer(c.ID())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error mounting storage for container %s", c.ID())
|
||||||
|
}
|
||||||
|
c.containerMountPoint = mountPoint
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if err2 := c.runtime.storageService.StopContainer(c.ID()); err2 != nil {
|
||||||
|
logrus.Errorf("Error unmounting storage for container %s: %v", c.ID(), err2)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.containerMountPoint = ""
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the OCI runtime spec we will use
|
||||||
|
c.runningSpec = new(spec.Spec)
|
||||||
|
deepcopier.Copy(c.config.Spec).To(c.runningSpec)
|
||||||
|
c.runningSpec.Root.Path = c.containerMountPoint
|
||||||
|
|
||||||
|
// TODO Add annotation for start time to spec
|
||||||
|
|
||||||
|
// Save the OCI spec to disk
|
||||||
|
jsonPath := filepath.Join(c.containerRunDir, "config.json")
|
||||||
|
fileJSON, err := json.Marshal(c.runningSpec)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error exporting runtime spec for container %s to JSON", c.ID())
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(jsonPath, fileJSON, 0644); err != nil {
|
||||||
|
return errors.Wrapf(err, "error writing runtime spec JSON to file for container %s", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the spec complete, do an OCI create
|
||||||
|
// TODO set cgroup parent in a sane fashion
|
||||||
|
if err := c.runtime.ociRuntime.createContainer(c, "/libpod_parent"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should flush this state to disk here
|
||||||
|
c.state = ContainerStateCreated
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts a container
|
// Start starts a container
|
||||||
func (c *Container) Start() error {
|
func (c *Container) Start() error {
|
||||||
return ErrNotImplemented
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if !c.valid {
|
||||||
|
return ErrCtrRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container must be created or stopped to be started
|
||||||
|
if !(c.state == ContainerStateCreated || c.state == ContainerStateStopped) {
|
||||||
|
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.runtime.ociRuntime.startContainer(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should flush state to disk here
|
||||||
|
c.state = ContainerStateRunning
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops a container
|
// Stop stops a container
|
||||||
|
@ -101,9 +294,13 @@ func (c *Container) Mount() (string, error) {
|
||||||
return "", ErrNotImplemented
|
return "", ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status gets a container's status
|
// Pause pauses a container
|
||||||
// TODO this should return relevant information about container state
|
func (c *Container) Pause() error {
|
||||||
func (c *Container) Status() error {
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpause unpauses a container
|
||||||
|
func (c *Container) Unpause() error {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ var (
|
||||||
// ErrImageExists indicated an image with the same ID already exists
|
// ErrImageExists indicated an image with the same ID already exists
|
||||||
ErrImageExists = errors.New("image already exists")
|
ErrImageExists = errors.New("image already exists")
|
||||||
|
|
||||||
|
// ErrCtrStateInvalid indicates a container is in an improper state for
|
||||||
|
// the requested operation
|
||||||
|
ErrCtrStateInvalid = errors.New("container state improper")
|
||||||
|
|
||||||
// ErrRuntimeFinalized indicates that the runtime has already been
|
// ErrRuntimeFinalized indicates that the runtime has already been
|
||||||
// created and cannot be modified
|
// created and cannot be modified
|
||||||
ErrRuntimeFinalized = errors.New("runtime has been finalized")
|
ErrRuntimeFinalized = errors.New("runtime has been finalized")
|
||||||
|
|
240
libpod/oci.go
Normal file
240
libpod/oci.go
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
package libpod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/cgroups"
|
||||||
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// TODO import these functions into libpod and remove the import
|
||||||
|
// Trying to keep libpod from depending on CRI-O code
|
||||||
|
"github.com/kubernetes-incubator/cri-o/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OCI code is undergoing heavy rewrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CgroupfsCgroupsManager represents cgroupfs native cgroup manager
|
||||||
|
CgroupfsCgroupsManager = "cgroupfs"
|
||||||
|
// SystemdCgroupsManager represents systemd native cgroup manager
|
||||||
|
SystemdCgroupsManager = "systemd"
|
||||||
|
|
||||||
|
// ContainerCreateTimeout represents the value of container creating timeout
|
||||||
|
ContainerCreateTimeout = 240 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// OCIRuntime represents an OCI-compatible runtime that libpod can call into
|
||||||
|
// to perform container operations
|
||||||
|
type OCIRuntime struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
conmonPath string
|
||||||
|
conmonEnv []string
|
||||||
|
cgroupManager string
|
||||||
|
exitsDir string
|
||||||
|
logSizeMax int64
|
||||||
|
noPivot bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncInfo is used to return data from monitor process to daemon
|
||||||
|
type syncInfo struct {
|
||||||
|
Pid int `json:"pid"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new OCI runtime with provided options
|
||||||
|
func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []string, cgroupManager string, exitsDir string, logSizeMax int64, noPivotRoot bool) (*OCIRuntime, error) {
|
||||||
|
runtime := new(OCIRuntime)
|
||||||
|
runtime.name = name
|
||||||
|
runtime.path = path
|
||||||
|
runtime.conmonPath = conmonPath
|
||||||
|
runtime.conmonEnv = conmonEnv
|
||||||
|
runtime.cgroupManager = cgroupManager
|
||||||
|
runtime.exitsDir = exitsDir
|
||||||
|
runtime.logSizeMax = logSizeMax
|
||||||
|
runtime.noPivot = noPivotRoot
|
||||||
|
|
||||||
|
if cgroupManager != CgroupfsCgroupsManager && cgroupManager != SystemdCgroupsManager {
|
||||||
|
return nil, errors.Wrapf(ErrInvalidArg, "invalid cgroup manager specified: %s", cgroupManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPipe creates a unix socket pair for communication
|
||||||
|
func newPipe() (parent *os.File, child *os.File, err error) {
|
||||||
|
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create systemd unit name for cgroup scopes
|
||||||
|
func createUnitName(prefix string, name string) string {
|
||||||
|
return fmt.Sprintf("%s-%s.scope", prefix, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContainer creates a container in the OCI runtime
|
||||||
|
// TODO terminal support for container
|
||||||
|
// Presently just ignoring conmon opts related to it
|
||||||
|
func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) error {
|
||||||
|
parentPipe, childPipe, err := newPipe()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating socket pair")
|
||||||
|
}
|
||||||
|
|
||||||
|
childStartPipe, parentStartPipe, err := newPipe()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating socket pair")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer parentPipe.Close()
|
||||||
|
defer parentStartPipe.Close()
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
if r.cgroupManager == SystemdCgroupsManager {
|
||||||
|
args = append(args, "-s")
|
||||||
|
}
|
||||||
|
args = append(args, "-c", ctr.ID())
|
||||||
|
args = append(args, "-u", ctr.ID())
|
||||||
|
args = append(args, "-r", r.path)
|
||||||
|
args = append(args, "-b", ctr.containerRunDir)
|
||||||
|
args = append(args, "-p", filepath.Join(ctr.containerRunDir, "pidfile"))
|
||||||
|
// TODO container log location should be configurable
|
||||||
|
// The default also likely shouldn't be this
|
||||||
|
args = append(args, "-l", filepath.Join(ctr.containerDir, "ctr.log"))
|
||||||
|
args = append(args, "--exit-dir", r.exitsDir)
|
||||||
|
if r.logSizeMax >= 0 {
|
||||||
|
args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
|
||||||
|
}
|
||||||
|
if r.noPivot {
|
||||||
|
args = append(args, "--no-pivot")
|
||||||
|
}
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"args": args,
|
||||||
|
}).Debugf("running conmon: %s", r.conmonPath)
|
||||||
|
|
||||||
|
cmd := exec.Command(r.conmonPath, args...)
|
||||||
|
cmd.Dir = ctr.containerRunDir
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setpgid: true,
|
||||||
|
}
|
||||||
|
// TODO this is probably a really bad idea for some uses
|
||||||
|
// Make this configurable
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe, childStartPipe)
|
||||||
|
// 0, 1 and 2 are stdin, stdout and stderr
|
||||||
|
cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
childPipe.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need childPipe on the parent side
|
||||||
|
childPipe.Close()
|
||||||
|
childStartPipe.Close()
|
||||||
|
|
||||||
|
// Move conmon to specified cgroup
|
||||||
|
if r.cgroupManager == SystemdCgroupsManager {
|
||||||
|
logrus.Infof("Running conmon under slice %s and unitName %s", cgroupParent, createUnitName("libpod-conmon", ctr.ID()))
|
||||||
|
if err = utils.RunUnderSystemdScope(cmd.Process.Pid, cgroupParent, createUnitName("libpod-conmon", ctr.ID())); err != nil {
|
||||||
|
logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(filepath.Join(cgroupParent, "/libpod-conmon-"+ctr.ID())), &spec.LinuxResources{})
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
||||||
|
} else {
|
||||||
|
// XXX: this defer does nothing as the cgroup can't be deleted cause
|
||||||
|
// it contains the conmon pid in tasks
|
||||||
|
// we need to remove this defer and delete the cgroup once conmon exits
|
||||||
|
// maybe need a conmon monitor?
|
||||||
|
defer control.Delete()
|
||||||
|
if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil {
|
||||||
|
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We set the cgroup, now the child can start creating children */
|
||||||
|
someData := []byte{0}
|
||||||
|
_, err = parentStartPipe.Write(someData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for initial setup and fork, and reap child */
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should do a defer r.deleteContainer(ctr) here if err != nil
|
||||||
|
// Need deleteContainer to be working first, though...
|
||||||
|
|
||||||
|
// Wait to get container pid from conmon
|
||||||
|
type syncStruct struct {
|
||||||
|
si *syncInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
ch := make(chan syncStruct)
|
||||||
|
go func() {
|
||||||
|
var si *syncInfo
|
||||||
|
if err = json.NewDecoder(parentPipe).Decode(&si); err != nil {
|
||||||
|
ch <- syncStruct{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- syncStruct{si: si}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case ss := <-ch:
|
||||||
|
if ss.err != nil {
|
||||||
|
return errors.Wrapf(ss.err, "error reading container (probably exited) json message")
|
||||||
|
}
|
||||||
|
logrus.Debugf("Received container pid: %d", ss.si.Pid)
|
||||||
|
if ss.si.Pid == -1 {
|
||||||
|
if ss.si.Message != "" {
|
||||||
|
return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message)
|
||||||
|
}
|
||||||
|
return errors.Wrapf(ErrInternal, "container create failed")
|
||||||
|
}
|
||||||
|
case <-time.After(ContainerCreateTimeout):
|
||||||
|
return errors.Wrapf(ErrInternal, "container creation timeout")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateContainerStatus retrieves the current status of the container from the
|
||||||
|
// runtime
|
||||||
|
func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// startContainer starts the given container
|
||||||
|
func (r *OCIRuntime) startContainer(ctr *Container) error {
|
||||||
|
// TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers?
|
||||||
|
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "start", ctr.ID()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO record start time in container struct
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -137,6 +137,7 @@ func WithConmonEnv(environment []string) RuntimeOption {
|
||||||
|
|
||||||
// WithCgroupManager specifies the manager implementation name which is used to
|
// WithCgroupManager specifies the manager implementation name which is used to
|
||||||
// handle cgroups for containers
|
// handle cgroups for containers
|
||||||
|
// Current valid values are "cgroupfs" and "systemd"
|
||||||
func WithCgroupManager(manager string) RuntimeOption {
|
func WithCgroupManager(manager string) RuntimeOption {
|
||||||
return func(rt *Runtime) error {
|
return func(rt *Runtime) error {
|
||||||
if rt.valid {
|
if rt.valid {
|
||||||
|
@ -149,6 +150,20 @@ func WithCgroupManager(manager string) RuntimeOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithExitsDir sets the directory that container exit files (containing exit
|
||||||
|
// codes) will be created by conmon
|
||||||
|
func WithExitsDir(dir string) RuntimeOption {
|
||||||
|
return func(rt *Runtime) error {
|
||||||
|
if rt.valid {
|
||||||
|
return ErrRuntimeFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.config.ExitsDir = dir
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithSELinux enables SELinux on the container server
|
// WithSELinux enables SELinux on the container server
|
||||||
func WithSELinux() RuntimeOption {
|
func WithSELinux() RuntimeOption {
|
||||||
return func(rt *Runtime) error {
|
return func(rt *Runtime) error {
|
||||||
|
@ -176,19 +191,74 @@ func WithPidsLimit(limit int64) RuntimeOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithMaxLogSize sets the maximum size of container logs
|
||||||
|
// Positive sizes are limits in bytes, -1 is unlimited
|
||||||
|
func WithMaxLogSize(limit int64) RuntimeOption {
|
||||||
|
return func(rt *Runtime) error {
|
||||||
|
if rt.valid {
|
||||||
|
return ErrRuntimeFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.config.MaxLogSize = limit
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoPivotRoot sets the runtime to use MS_MOVE instead of PIVOT_ROOT when
|
||||||
|
// starting containers
|
||||||
|
func WithNoPivotRoot(noPivot bool) RuntimeOption {
|
||||||
|
return func(rt *Runtime) error {
|
||||||
|
if rt.valid {
|
||||||
|
return ErrRuntimeFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.config.NoPivotRoot = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Container Creation Options
|
// Container Creation Options
|
||||||
|
|
||||||
// WithRootFSFromPath uses the given path as a container's root filesystem
|
// WithRootFSFromPath uses the given path as a container's root filesystem
|
||||||
// No further setup is performed on this path
|
// No further setup is performed on this path
|
||||||
func WithRootFSFromPath(path string) CtrCreateOption {
|
func WithRootFSFromPath(path string) CtrCreateOption {
|
||||||
return ctrNotImplemented
|
return func(ctr *Container) error {
|
||||||
|
if ctr.valid {
|
||||||
|
return ErrCtrFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctr.config.RootfsDir != nil || ctr.config.RootfsImageID != nil || ctr.config.RootfsImageName != nil {
|
||||||
|
return fmt.Errorf("container already configured to with rootfs")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr.config.RootfsDir = &path
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithRootFSFromImage sets up a fresh root filesystem using the given image
|
// WithRootFSFromImage sets up a fresh root filesystem using the given image
|
||||||
// If useImageConfig is specified, image volumes, environment variables, and
|
// If useImageConfig is specified, image volumes, environment variables, and
|
||||||
// other configuration from the image will be added to the config
|
// other configuration from the image will be added to the config
|
||||||
func WithRootFSFromImage(image string, useImageConfig bool) CtrCreateOption {
|
// TODO: Replace image name and ID with a libpod.Image struct when that is finished
|
||||||
return ctrNotImplemented
|
func WithRootFSFromImage(imageID string, imageName string, useImageConfig bool) CtrCreateOption {
|
||||||
|
return func(ctr *Container) error {
|
||||||
|
if ctr.valid {
|
||||||
|
return ErrCtrFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctr.config.RootfsDir != nil || ctr.config.RootfsImageID != nil || ctr.config.RootfsImageName != nil {
|
||||||
|
return fmt.Errorf("container already configured to with rootfs")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr.config.RootfsImageID = &imageID
|
||||||
|
ctr.config.RootfsImageName = &imageName
|
||||||
|
ctr.config.UseImageConfig = useImageConfig
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSharedNamespaces sets a container to share namespaces with another
|
// WithSharedNamespaces sets a container to share namespaces with another
|
||||||
|
@ -203,7 +273,7 @@ func WithSharedNamespaces(from *Container, namespaces map[string]string) CtrCrea
|
||||||
// WithPod adds the container to a pod
|
// WithPod adds the container to a pod
|
||||||
func (r *Runtime) WithPod(pod *Pod) CtrCreateOption {
|
func (r *Runtime) WithPod(pod *Pod) CtrCreateOption {
|
||||||
return func(ctr *Container) error {
|
return func(ctr *Container) error {
|
||||||
if !ctr.valid {
|
if ctr.valid {
|
||||||
return ErrCtrFinalized
|
return ErrCtrFinalized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,11 +311,11 @@ func WithAnnotations(annotations map[string]string) CtrCreateOption {
|
||||||
// WithName sets the container's name
|
// WithName sets the container's name
|
||||||
func WithName(name string) CtrCreateOption {
|
func WithName(name string) CtrCreateOption {
|
||||||
return func(ctr *Container) error {
|
return func(ctr *Container) error {
|
||||||
if !ctr.valid {
|
if ctr.valid {
|
||||||
return ErrCtrFinalized
|
return ErrCtrFinalized
|
||||||
}
|
}
|
||||||
|
|
||||||
ctr.name = name
|
ctr.config.Name = name
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,11 +50,11 @@ func (p *Pod) addContainer(ctr *Container) error {
|
||||||
return ErrPodRemoved
|
return ErrPodRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := p.containers[ctr.id]; ok {
|
if _, ok := p.containers[ctr.ID()]; ok {
|
||||||
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in pod %s", ctr.id, p.id)
|
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in pod %s", ctr.ID(), p.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.containers[ctr.id] = ctr
|
p.containers[ctr.ID()] = ctr
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -69,11 +69,11 @@ func (p *Pod) removeContainer(ctr *Container) error {
|
||||||
return ErrPodRemoved
|
return ErrPodRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := p.containers[ctr.id]; !ok {
|
if _, ok := p.containers[ctr.ID()]; !ok {
|
||||||
return errors.Wrapf(ErrNoSuchCtr, "no container with id %s in pod %s", ctr.id, p.id)
|
return errors.Wrapf(ErrNoSuchCtr, "no container with id %s in pod %s", ctr.ID(), p.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(p.containers, ctr.id)
|
delete(p.containers, ctr.ID())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
is "github.com/containers/image/storage"
|
is "github.com/containers/image/storage"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/kubernetes-incubator/cri-o/server/apparmor"
|
|
||||||
"github.com/kubernetes-incubator/cri-o/server/seccomp"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/ulule/deepcopier"
|
"github.com/ulule/deepcopier"
|
||||||
)
|
)
|
||||||
|
@ -18,14 +17,14 @@ type RuntimeOption func(*Runtime) error
|
||||||
|
|
||||||
// Runtime is the core libpod runtime
|
// Runtime is the core libpod runtime
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
config *RuntimeConfig
|
config *RuntimeConfig
|
||||||
state State
|
state State
|
||||||
store storage.Store
|
store storage.Store
|
||||||
imageContext *types.SystemContext
|
storageService *storageService
|
||||||
apparmorEnabled bool
|
imageContext *types.SystemContext
|
||||||
seccompEnabled bool
|
ociRuntime *OCIRuntime
|
||||||
valid bool
|
valid bool
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuntimeConfig contains configuration options used to set up the runtime
|
// RuntimeConfig contains configuration options used to set up the runtime
|
||||||
|
@ -39,8 +38,11 @@ type RuntimeConfig struct {
|
||||||
ConmonPath string
|
ConmonPath string
|
||||||
ConmonEnvVars []string
|
ConmonEnvVars []string
|
||||||
CgroupManager string
|
CgroupManager string
|
||||||
|
ExitsDir string
|
||||||
SelinuxEnabled bool
|
SelinuxEnabled bool
|
||||||
PidsLimit int64
|
PidsLimit int64
|
||||||
|
MaxLogSize int64
|
||||||
|
NoPivotRoot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -54,8 +56,11 @@ var (
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
},
|
},
|
||||||
CgroupManager: "cgroupfs",
|
CgroupManager: "cgroupfs",
|
||||||
|
ExitsDir: "/var/run/libpod/exits",
|
||||||
SelinuxEnabled: false,
|
SelinuxEnabled: false,
|
||||||
PidsLimit: 1024,
|
PidsLimit: 1024,
|
||||||
|
MaxLogSize: -1,
|
||||||
|
NoPivotRoot: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -83,6 +88,14 @@ func NewRuntime(options ...RuntimeOption) (*Runtime, error) {
|
||||||
runtime.store = store
|
runtime.store = store
|
||||||
is.Transport.SetStore(store)
|
is.Transport.SetStore(store)
|
||||||
|
|
||||||
|
// TODO remove StorageImageServer and make its functions work directly
|
||||||
|
// on Runtime (or convert to something that satisfies an image)
|
||||||
|
storageService, err := getStorageService(runtime.store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
runtime.storageService = storageService
|
||||||
|
|
||||||
// Set up containers/image
|
// Set up containers/image
|
||||||
runtime.imageContext = &types.SystemContext{
|
runtime.imageContext = &types.SystemContext{
|
||||||
SignaturePolicyPath: runtime.config.SignaturePolicyPath,
|
SignaturePolicyPath: runtime.config.SignaturePolicyPath,
|
||||||
|
@ -95,8 +108,24 @@ func NewRuntime(options ...RuntimeOption) (*Runtime, error) {
|
||||||
}
|
}
|
||||||
runtime.state = state
|
runtime.state = state
|
||||||
|
|
||||||
runtime.seccompEnabled = seccomp.IsEnabled()
|
// Make an OCI runtime to perform container operations
|
||||||
runtime.apparmorEnabled = apparmor.IsEnabled()
|
ociRuntime, err := newOCIRuntime("runc", runtime.config.RuntimePath,
|
||||||
|
runtime.config.ConmonPath, runtime.config.ConmonEnvVars,
|
||||||
|
runtime.config.CgroupManager, runtime.config.ExitsDir,
|
||||||
|
runtime.config.MaxLogSize, runtime.config.NoPivotRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
runtime.ociRuntime = ociRuntime
|
||||||
|
|
||||||
|
// Make the directory that will hold container exit files
|
||||||
|
if err := os.MkdirAll(runtime.config.ExitsDir, 0755); err != nil {
|
||||||
|
// The directory is allowed to exist
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return nil, errors.Wrapf(err, "error creating container exit files directory %s",
|
||||||
|
runtime.config.ExitsDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mark the runtime as valid - ready to be used, cannot be modified
|
// Mark the runtime as valid - ready to be used, cannot be modified
|
||||||
// further
|
// further
|
||||||
|
|
|
@ -38,6 +38,13 @@ func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (*Co
|
||||||
}
|
}
|
||||||
|
|
||||||
ctr.valid = true
|
ctr.valid = true
|
||||||
|
ctr.state = ContainerStateConfigured
|
||||||
|
ctr.runtime = r
|
||||||
|
|
||||||
|
if err := ctr.setupStorage(); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error configuring storage for container")
|
||||||
|
}
|
||||||
|
// TODO: once teardownStorage is implemented, do a defer here that tears down storage is AddContainer fails
|
||||||
|
|
||||||
if err := r.state.AddContainer(ctr); err != nil {
|
if err := r.state.AddContainer(ctr); err != nil {
|
||||||
// If we joined a pod, remove ourself from it
|
// If we joined a pod, remove ourself from it
|
||||||
|
@ -77,10 +84,16 @@ func (r *Runtime) RemoveContainer(c *Container, force bool) error {
|
||||||
// TODO check container status and unmount storage
|
// TODO check container status and unmount storage
|
||||||
// TODO check that no other containers depend on this container's
|
// TODO check that no other containers depend on this container's
|
||||||
// namespaces
|
// namespaces
|
||||||
if err := c.Status(); err != nil {
|
status, err := c.State()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A container cannot be removed if it is running
|
||||||
|
if status == ContainerStateRunning {
|
||||||
|
return errors.Wrapf(ErrCtrStateInvalid, "cannot remove container %s as it is running", c.ID())
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.state.RemoveContainer(c); err != nil {
|
if err := r.state.RemoveContainer(c); err != nil {
|
||||||
return errors.Wrapf(err, "error removing container from state")
|
return errors.Wrapf(err, "error removing container from state")
|
||||||
}
|
}
|
||||||
|
@ -185,6 +198,7 @@ func (r *Runtime) getContainersWithImage(imageID string) ([]storage.Container, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeMultipleContainers deletes a list of containers from the store
|
// removeMultipleContainers deletes a list of containers from the store
|
||||||
|
// TODO refactor this to remove libpod Containers
|
||||||
func (r *Runtime) removeMultipleContainers(containers []storage.Container) error {
|
func (r *Runtime) removeMultipleContainers(containers []storage.Container) error {
|
||||||
for _, ctr := range containers {
|
for _, ctr := range containers {
|
||||||
if err := r.store.DeleteContainer(ctr.ID); err != nil {
|
if err := r.store.DeleteContainer(ctr.ID); err != nil {
|
||||||
|
@ -193,3 +207,8 @@ func (r *Runtime) removeMultipleContainers(containers []storage.Container) error
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerConfigToDisk saves a container's nonvolatile configuration to disk
|
||||||
|
func (r *Runtime) containerConfigToDisk(ctr *Container) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
263
libpod/storage.go
Normal file
263
libpod/storage.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
package libpod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
istorage "github.com/containers/image/storage"
|
||||||
|
"github.com/containers/image/types"
|
||||||
|
"github.com/containers/storage"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storageService struct {
|
||||||
|
store storage.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStorageService returns a storageService which can create container root
|
||||||
|
// filesystems from images
|
||||||
|
func getStorageService(store storage.Store) (*storageService, error) {
|
||||||
|
return &storageService{store: store}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerInfo wraps a subset of information about a container: its ID and
|
||||||
|
// the locations of its nonvolatile and volatile per-container directories,
|
||||||
|
// along with a copy of the configuration blob from the image that was used to
|
||||||
|
// create the container, if the image had a configuration.
|
||||||
|
type ContainerInfo struct {
|
||||||
|
ID string
|
||||||
|
Dir string
|
||||||
|
RunDir string
|
||||||
|
Config *v1.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeContainerMetadata is the structure that we encode as JSON and store
|
||||||
|
// in the metadata field of storage.Container objects. It is used for
|
||||||
|
// specifying attributes of pod sandboxes and containers when they are being
|
||||||
|
// created, and allows a container's MountLabel, and possibly other values, to
|
||||||
|
// be modified in one read/write cycle via calls to
|
||||||
|
// RuntimeServer.ContainerMetadata, RuntimeContainerMetadata.SetMountLabel,
|
||||||
|
// and RuntimeServer.SetContainerMetadata.
|
||||||
|
type RuntimeContainerMetadata struct {
|
||||||
|
// The provided name and the ID of the image that was used to
|
||||||
|
// instantiate the container.
|
||||||
|
ImageName string `json:"image-name"` // Applicable to both PodSandboxes and Containers
|
||||||
|
ImageID string `json:"image-id"` // Applicable to both PodSandboxes and Containers
|
||||||
|
// The container's name, which for an infrastructure container is usually PodName + "-infra".
|
||||||
|
ContainerName string `json:"name"` // Applicable to both PodSandboxes and Containers, mandatory
|
||||||
|
CreatedAt int64 `json:"created-at"` // Applicable to both PodSandboxes and Containers
|
||||||
|
MountLabel string `json:"mountlabel,omitempty"` // Applicable to both PodSandboxes and Containers
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMountLabel updates the mount label held by a RuntimeContainerMetadata
|
||||||
|
// object.
|
||||||
|
func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) {
|
||||||
|
metadata.MountLabel = mountLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContainerStorage creates the storage end of things. We already have the container spec created
|
||||||
|
// TO-DO We should be passing in an KpodImage object in the future.
|
||||||
|
func (r *storageService) CreateContainerStorage(systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string) (ContainerInfo, error) {
|
||||||
|
var ref types.ImageReference
|
||||||
|
if imageName == "" && imageID == "" {
|
||||||
|
return ContainerInfo{}, ErrEmptyID
|
||||||
|
}
|
||||||
|
if containerName == "" {
|
||||||
|
return ContainerInfo{}, ErrEmptyID
|
||||||
|
}
|
||||||
|
//// Check if we have the specified image.
|
||||||
|
ref, err := istorage.Transport.ParseStoreReference(r.store, imageName)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
img, err := istorage.Transport.GetStoreImage(r.store, ref)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
// Pull out a copy of the image's configuration.
|
||||||
|
image, err := ref.NewImage(systemContext)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
defer image.Close()
|
||||||
|
|
||||||
|
imageConfig, err := image.OCIConfig()
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the image name and ID.
|
||||||
|
if imageName == "" && len(img.Names) > 0 {
|
||||||
|
imageName = img.Names[0]
|
||||||
|
}
|
||||||
|
imageID = img.ID
|
||||||
|
|
||||||
|
// Build metadata to store with the container.
|
||||||
|
metadata := RuntimeContainerMetadata{
|
||||||
|
ImageName: imageName,
|
||||||
|
ImageID: imageID,
|
||||||
|
ContainerName: containerName,
|
||||||
|
CreatedAt: time.Now().Unix(),
|
||||||
|
MountLabel: mountLabel,
|
||||||
|
}
|
||||||
|
mdata, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the container.
|
||||||
|
names := []string{containerName}
|
||||||
|
|
||||||
|
container, err := r.store.CreateContainer(containerID, names, img.ID, "", string(mdata), nil)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err)
|
||||||
|
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("created container %q", container.ID)
|
||||||
|
|
||||||
|
// If anything fails after this point, we need to delete the incomplete
|
||||||
|
// container before returning.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if err2 := r.store.DeleteContainer(container.ID); err2 != nil {
|
||||||
|
logrus.Infof("%v deleting partially-created container %q", err2, container.ID)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logrus.Infof("deleted partially-created container %q", container.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Add a name to the container's layer so that it's easier to follow
|
||||||
|
// what's going on if we're just looking at the storage-eye view of things.
|
||||||
|
layerName := metadata.ContainerName + "-layer"
|
||||||
|
names, err = r.store.Names(container.LayerID)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
names = append(names, layerName)
|
||||||
|
err = r.store.SetNames(container.LayerID, names)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out where the container work directories are, so that we can return them.
|
||||||
|
containerDir, err := r.store.ContainerDirectory(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("container %q has work directory %q", container.ID, containerDir)
|
||||||
|
|
||||||
|
containerRunDir, err := r.store.ContainerRunDirectory(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ContainerInfo{}, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("container %q has run directory %q", container.ID, containerRunDir)
|
||||||
|
|
||||||
|
return ContainerInfo{
|
||||||
|
ID: container.ID, // not needed
|
||||||
|
Dir: containerDir,
|
||||||
|
RunDir: containerRunDir,
|
||||||
|
Config: imageConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) DeleteContainer(idOrName string) error {
|
||||||
|
if idOrName == "" {
|
||||||
|
return ErrEmptyID
|
||||||
|
}
|
||||||
|
container, err := r.store.Container(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = r.store.DeleteContainer(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to delete container %q: %v", container.ID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error {
|
||||||
|
mdata, err := json.Marshal(&metadata)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to encode metadata for %q: %v", idOrName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.store.SetMetadata(idOrName, string(mdata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) {
|
||||||
|
metadata := RuntimeContainerMetadata{}
|
||||||
|
mdata, err := r.store.Metadata(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return metadata, err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(mdata), &metadata); err != nil {
|
||||||
|
return metadata, err
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) StartContainer(idOrName string) (string, error) {
|
||||||
|
container, err := r.store.Container(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||||
|
return "", ErrNoSuchCtr
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
metadata := RuntimeContainerMetadata{}
|
||||||
|
if err = json.Unmarshal([]byte(container.Metadata), &metadata); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
mountPoint, err := r.store.Mount(container.ID, metadata.MountLabel)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to mount container %q: %v", container.ID, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
logrus.Debugf("mounted container %q at %q", container.ID, mountPoint)
|
||||||
|
return mountPoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) StopContainer(idOrName string) error {
|
||||||
|
if idOrName == "" {
|
||||||
|
return ErrEmptyID
|
||||||
|
}
|
||||||
|
container, err := r.store.Container(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = r.store.Unmount(container.ID)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("failed to unmount container %q: %v", container.ID, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("unmounted container %q", container.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) GetWorkDir(id string) (string, error) {
|
||||||
|
container, err := r.store.Container(id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||||
|
return "", ErrNoSuchCtr
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.store.ContainerDirectory(container.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storageService) GetRunDir(id string) (string, error) {
|
||||||
|
container, err := r.store.Container(id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||||
|
return "", ErrNoSuchCtr
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.store.ContainerRunDirectory(container.ID)
|
||||||
|
}
|
Loading…
Reference in a new issue