457 lines
17 KiB
Go
457 lines
17 KiB
Go
|
package storage
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"time"
|
||
|
|
||
|
"github.com/Sirupsen/logrus"
|
||
|
"github.com/containers/image/copy"
|
||
|
istorage "github.com/containers/image/storage"
|
||
|
"github.com/containers/image/transports"
|
||
|
"github.com/containers/image/types"
|
||
|
"github.com/containers/storage/storage"
|
||
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrInvalidPodName is returned when a pod name specified to a
|
||
|
// function call is found to be invalid (most often, because it's
|
||
|
// empty).
|
||
|
ErrInvalidPodName = errors.New("invalid pod name")
|
||
|
// ErrInvalidImageName is returned when an image name specified to a
|
||
|
// function call is found to be invalid (most often, because it's
|
||
|
// empty).
|
||
|
ErrInvalidImageName = errors.New("invalid image name")
|
||
|
// ErrInvalidContainerName is returned when a container name specified
|
||
|
// to a function call is found to be invalid (most often, because it's
|
||
|
// empty).
|
||
|
ErrInvalidContainerName = errors.New("invalid container name")
|
||
|
// ErrInvalidSandboxID is returned when a sandbox ID specified to a
|
||
|
// function call is found to be invalid (because it's either
|
||
|
// empty or doesn't match a valid sandbox).
|
||
|
ErrInvalidSandboxID = errors.New("invalid sandbox ID")
|
||
|
// ErrInvalidContainerID is returned when a container ID specified to a
|
||
|
// function call is found to be invalid (because it's either
|
||
|
// empty or doesn't match a valid container).
|
||
|
ErrInvalidContainerID = errors.New("invalid container ID")
|
||
|
)
|
||
|
|
||
|
type runtimeService struct {
|
||
|
image ImageServer
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
// RuntimeServer wraps up various CRI-related activities into a reusable
|
||
|
// implementation.
|
||
|
type RuntimeServer interface {
|
||
|
// CreatePodSandbox creates a pod infrastructure container, using the
|
||
|
// specified PodID for the infrastructure container's ID. In the CRI
|
||
|
// view of things, a sandbox is distinct from its containers, including
|
||
|
// its infrastructure container, but at this level the sandbox is
|
||
|
// essentially the same as its infrastructure container, with a
|
||
|
// container's membership in a pod being signified by it listing the
|
||
|
// same pod ID in its metadata that the pod's other members do, and
|
||
|
// with the pod's infrastructure container having the same value for
|
||
|
// both its pod's ID and its container ID.
|
||
|
// Pointer arguments can be nil. Either the image name or ID can be
|
||
|
// omitted, but not both. All other arguments are required.
|
||
|
CreatePodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, metadataName, uid, namespace string, attempt uint32, copyOptions *copy.Options) (ContainerInfo, error)
|
||
|
// RemovePodSandbox deletes a pod sandbox's infrastructure container.
|
||
|
// The CRI expects that a sandbox can't be removed unless its only
|
||
|
// container is its infrastructure container, but we don't enforce that
|
||
|
// here, since we're just keeping track of it for higher level APIs.
|
||
|
RemovePodSandbox(idOrName string) error
|
||
|
|
||
|
// GetContainerMetadata returns the metadata we've stored for a container.
|
||
|
GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error)
|
||
|
// SetContainerMetadata updates the metadata we've stored for a container.
|
||
|
SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error
|
||
|
|
||
|
// CreateContainer creates a container with the specified ID.
|
||
|
// Pointer arguments can be nil. Either the image name or ID can be
|
||
|
// omitted, but not both. All other arguments are required.
|
||
|
CreateContainer(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName string, attempt uint32, mountLabel string, copyOptions *copy.Options) (ContainerInfo, error)
|
||
|
// DeleteContainer deletes a container, unmounting it first if need be.
|
||
|
DeleteContainer(idOrName string) error
|
||
|
|
||
|
// StartContainer makes sure a container's filesystem is mounted, and
|
||
|
// returns the location of its root filesystem, which is not guaranteed
|
||
|
// by lower-level drivers to never change.
|
||
|
StartContainer(idOrName string) (string, error)
|
||
|
// StopContainer attempts to unmount a container's root filesystem,
|
||
|
// freeing up any kernel resources which may be limited.
|
||
|
StopContainer(idOrName string) error
|
||
|
|
||
|
// GetWorkDir returns the path of a nonvolatile directory on the
|
||
|
// filesystem (somewhere under the Store's Root directory) which can be
|
||
|
// used to store arbitrary data that is specific to the container. It
|
||
|
// will be removed automatically when the container is deleted.
|
||
|
GetWorkDir(id string) (string, error)
|
||
|
// GetRunDir returns the path of a volatile directory (does not survive
|
||
|
// the host rebooting, somewhere under the Store's RunRoot directory)
|
||
|
// on the filesystem which can be used to store arbitrary data that is
|
||
|
// specific to the container. It will be removed automatically when
|
||
|
// the container is deleted.
|
||
|
GetRunDir(id string) (string, error)
|
||
|
}
|
||
|
|
||
|
// 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.GetContainerMetadata, RuntimeContainerMetadata.SetMountLabel,
|
||
|
// and RuntimeServer.SetContainerMetadata.
|
||
|
type RuntimeContainerMetadata struct {
|
||
|
// Pod is true if this is the pod's infrastructure container.
|
||
|
Pod bool `json:"pod,omitempty"` // Applicable to both PodSandboxes and Containers
|
||
|
// The pod's name and ID, kept for use by upper layers in determining
|
||
|
// which containers belong to which pods.
|
||
|
PodName string `json:"pod-name"` // Applicable to both PodSandboxes and Containers, mandatory
|
||
|
PodID string `json:"pod-id"` // Applicable to both PodSandboxes and Containers, mandatory
|
||
|
// 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
|
||
|
// The name as originally specified in PodSandbox or Container CRI metadata.
|
||
|
MetadataName string `json:"metadata-name"` // Applicable to both PodSandboxes and Containers, mandatory
|
||
|
UID string `json:"uid,omitempty"` // Only applicable to pods
|
||
|
Namespace string `json:"namespace,omitempty"` // Only applicable to pods
|
||
|
Attempt uint32 `json:"attempt,omitempty"` // Applicable to both PodSandboxes and Containers
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName, uid, namespace string, attempt uint32, mountLabel string, options *copy.Options) (ContainerInfo, error) {
|
||
|
var ref types.ImageReference
|
||
|
if podName == "" || podID == "" {
|
||
|
return ContainerInfo{}, ErrInvalidPodName
|
||
|
}
|
||
|
if imageName == "" && imageID == "" {
|
||
|
return ContainerInfo{}, ErrInvalidImageName
|
||
|
}
|
||
|
if containerName == "" {
|
||
|
return ContainerInfo{}, ErrInvalidContainerName
|
||
|
}
|
||
|
if metadataName == "" {
|
||
|
metadataName = containerName
|
||
|
}
|
||
|
|
||
|
// Check if we have the specified image.
|
||
|
ref, err := istorage.Transport.ParseStoreReference(r.image.GetStore(), imageName)
|
||
|
if err != nil {
|
||
|
// Maybe it's some other transport's copy of the image?
|
||
|
otherRef, err2 := transports.ParseImageName(imageName)
|
||
|
if err2 == nil && otherRef.DockerReference() != nil {
|
||
|
ref, err = istorage.Transport.ParseStoreReference(r.image.GetStore(), otherRef.DockerReference().FullName())
|
||
|
}
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
}
|
||
|
img, err := istorage.Transport.GetStoreImage(r.image.GetStore(), ref)
|
||
|
if img == nil && err == storage.ErrImageUnknown && imageID != "" {
|
||
|
ref, err = istorage.Transport.ParseStoreReference(r.image.GetStore(), "@"+imageID)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
img, err = istorage.Transport.GetStoreImage(r.image.GetStore(), ref)
|
||
|
}
|
||
|
if err != nil && err != storage.ErrImageUnknown {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
|
||
|
// Pull the image down if we don't already have it.
|
||
|
if err == storage.ErrImageUnknown {
|
||
|
image := imageID
|
||
|
if imageName != "" {
|
||
|
image = imageName
|
||
|
}
|
||
|
if image == "" {
|
||
|
return ContainerInfo{}, ErrInvalidImageName
|
||
|
}
|
||
|
logrus.Debugf("couldn't find image %q, retrieving it", image)
|
||
|
ref, err = r.image.PullImage(systemContext, image, options)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
img, err = istorage.Transport.GetStoreImage(r.image.GetStore(), ref)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
logrus.Debugf("successfully pulled image %q", image)
|
||
|
}
|
||
|
|
||
|
// Pull out a copy of the image's configuration.
|
||
|
image, err := ref.NewImage(systemContext)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
defer image.Close()
|
||
|
var imageConfig *v1.Image
|
||
|
configBlob, err := image.ConfigBlob()
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
if len(configBlob) > 0 {
|
||
|
config := v1.Image{}
|
||
|
err = json.Unmarshal(configBlob, &config)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
imageConfig = &config
|
||
|
}
|
||
|
|
||
|
// 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{
|
||
|
Pod: containerID == podID,
|
||
|
PodName: podName,
|
||
|
PodID: podID,
|
||
|
ImageName: imageName,
|
||
|
ImageID: imageID,
|
||
|
ContainerName: containerName,
|
||
|
MetadataName: metadataName,
|
||
|
UID: uid,
|
||
|
Namespace: namespace,
|
||
|
Attempt: attempt,
|
||
|
CreatedAt: time.Now().Unix(),
|
||
|
MountLabel: mountLabel,
|
||
|
}
|
||
|
mdata, err := json.Marshal(&metadata)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
|
||
|
// Build the container.
|
||
|
names := []string{metadata.ContainerName}
|
||
|
if metadata.Pod {
|
||
|
names = append(names, metadata.PodName)
|
||
|
}
|
||
|
container, err := r.image.GetStore().CreateContainer(containerID, names, img.ID, "", string(mdata), nil)
|
||
|
if err != nil {
|
||
|
if metadata.Pod {
|
||
|
logrus.Debugf("failed to create pod sandbox %s(%s): %v", metadata.PodName, metadata.PodID, err)
|
||
|
} else {
|
||
|
logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err)
|
||
|
}
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
if metadata.Pod {
|
||
|
logrus.Debugf("created pod sandbox %q", container.ID)
|
||
|
} else {
|
||
|
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.image.GetStore().DeleteContainer(container.ID); err2 != nil {
|
||
|
if metadata.Pod {
|
||
|
logrus.Infof("%v deleting partially-created pod sandbox %q", err2, container.ID)
|
||
|
} else {
|
||
|
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.image.GetStore().GetNames(container.LayerID)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
names = append(names, layerName)
|
||
|
err = r.image.GetStore().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.image.GetStore().GetContainerDirectory(container.ID)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
if metadata.Pod {
|
||
|
logrus.Debugf("pod sandbox %q has work directory %q", container.ID, containerDir)
|
||
|
} else {
|
||
|
logrus.Debugf("container %q has work directory %q", container.ID, containerDir)
|
||
|
}
|
||
|
|
||
|
containerRunDir, err := r.image.GetStore().GetContainerRunDirectory(container.ID)
|
||
|
if err != nil {
|
||
|
return ContainerInfo{}, err
|
||
|
}
|
||
|
if metadata.Pod {
|
||
|
logrus.Debugf("pod sandbox %q has run directory %q", container.ID, containerRunDir)
|
||
|
} else {
|
||
|
logrus.Debugf("container %q has run directory %q", container.ID, containerRunDir)
|
||
|
}
|
||
|
|
||
|
return ContainerInfo{
|
||
|
ID: container.ID,
|
||
|
Dir: containerDir,
|
||
|
RunDir: containerRunDir,
|
||
|
Config: imageConfig,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, metadataName, uid, namespace string, attempt uint32, copyOptions *copy.Options) (ContainerInfo, error) {
|
||
|
return r.createContainerOrPodSandbox(systemContext, podName, podID, imageName, imageID, containerName, podID, metadataName, uid, namespace, attempt, "", copyOptions)
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName string, attempt uint32, mountLabel string, copyOptions *copy.Options) (ContainerInfo, error) {
|
||
|
return r.createContainerOrPodSandbox(systemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName, "", "", attempt, mountLabel, copyOptions)
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) RemovePodSandbox(idOrName string) error {
|
||
|
container, err := r.image.GetStore().GetContainer(idOrName)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return ErrInvalidSandboxID
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
err = r.image.GetStore().DeleteContainer(container.ID)
|
||
|
if err != nil {
|
||
|
logrus.Debugf("failed to delete pod sandbox %q: %v", container.ID, err)
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) DeleteContainer(idOrName string) error {
|
||
|
container, err := r.image.GetStore().GetContainer(idOrName)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return ErrInvalidContainerID
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
err = r.image.GetStore().DeleteContainer(container.ID)
|
||
|
if err != nil {
|
||
|
logrus.Debugf("failed to delete container %q: %v", container.ID, err)
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) 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.image.GetStore().SetMetadata(idOrName, string(mdata))
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) {
|
||
|
metadata := RuntimeContainerMetadata{}
|
||
|
mdata, err := r.image.GetStore().GetMetadata(idOrName)
|
||
|
if err != nil {
|
||
|
return metadata, err
|
||
|
}
|
||
|
if err = json.Unmarshal([]byte(mdata), &metadata); err != nil {
|
||
|
return metadata, err
|
||
|
}
|
||
|
return metadata, nil
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) StartContainer(idOrName string) (string, error) {
|
||
|
container, err := r.image.GetStore().GetContainer(idOrName)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return "", ErrInvalidContainerID
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
metadata := RuntimeContainerMetadata{}
|
||
|
if err = json.Unmarshal([]byte(container.Metadata), &metadata); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
mountPoint, err := r.image.GetStore().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 *runtimeService) StopContainer(idOrName string) error {
|
||
|
container, err := r.image.GetStore().GetContainer(idOrName)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return ErrInvalidContainerID
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
err = r.image.GetStore().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 *runtimeService) GetWorkDir(id string) (string, error) {
|
||
|
container, err := r.image.GetStore().GetContainer(id)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return "", ErrInvalidContainerID
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
return r.image.GetStore().GetContainerDirectory(container.ID)
|
||
|
}
|
||
|
|
||
|
func (r *runtimeService) GetRunDir(id string) (string, error) {
|
||
|
container, err := r.image.GetStore().GetContainer(id)
|
||
|
if err != nil {
|
||
|
if err == storage.ErrContainerUnknown {
|
||
|
return "", ErrInvalidContainerID
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
return r.image.GetStore().GetContainerRunDirectory(container.ID)
|
||
|
}
|
||
|
|
||
|
// GetRuntimeService returns a RuntimeServer that uses the passed-in image
|
||
|
// service to pull and manage images, and its store to manage containers based
|
||
|
// on those images.
|
||
|
func GetRuntimeService(image ImageServer) RuntimeServer {
|
||
|
return &runtimeService{
|
||
|
image: image,
|
||
|
}
|
||
|
}
|