Merge pull request #1116 from mheon/namespaces

Add Pod and Container namespaces
This commit is contained in:
Daniel J Walsh
2018-07-25 08:47:35 -04:00
committed by GitHub
21 changed files with 1901 additions and 75 deletions

View File

@ -88,6 +88,10 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions
// TODO CLI flags for image config?
// TODO CLI flag for signature policy?
if c.GlobalIsSet("namespace") {
options = append(options, libpod.WithNamespace(c.GlobalString("namespace")))
}
if c.GlobalIsSet("runtime") {
options = append(options, libpod.WithOCIRuntime(c.GlobalString("runtime")))
}

View File

@ -172,6 +172,11 @@ func main() {
Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic",
Value: "error",
},
cli.StringFlag{
Name: "namespace",
Usage: "set the libpod namespace, used to create separate views of the containers and pods on the system",
Value: "",
},
cli.StringFlag{
Name: "root",
Usage: "path to the root directory in which data, including images, is stored",

View File

@ -2224,6 +2224,7 @@ _podman_podman() {
--storage-driver
--storage-opt
--log-level
--namespace
"
local boolean_options="
--help -h

View File

@ -37,7 +37,12 @@ Path to where the cpu performance results should be written
**--log-level**
log messages above specified level: debug, info, warn, error (default), fatal or panic
Log messages above specified level: debug, info, warn, error (default), fatal or panic
**--namespace**
Set libpod namespace. Namespaces are used to separate groups of containers and pods in libpod's state.
When namespace is set, created containers and pods will join the given namespace, and only containers and pods in the given namespace will be visible to Podman.
**--root**=**value**

View File

@ -57,3 +57,11 @@ cni_plugin_dir = [
"/usr/lib/cni",
"/opt/cni/bin"
]
# Default libpod namespace
# If libpod is joined to a namespace, it will see only containers and pods
# that were created in the same namespace, and will create new containers and
# pods in that namespace.
# The default namespace is "", which corresponds to no namespace. When no
# namespace is set, all containers and pods are visible.
#namespace = ""

View File

@ -1,28 +1,64 @@
package libpod
import (
"bytes"
"encoding/json"
"os"
"strings"
"github.com/boltdb/bolt"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// BoltState is a state implementation backed by a Bolt DB
type BoltState struct {
valid bool
dbPath string
namespace string
namespaceBytes []byte
lockDir string
runtime *Runtime
}
// A brief description of the format of the BoltDB state:
// At the top level, the following buckets are created:
// - idRegistryBkt: Maps ID to Name for containers and pods.
// Used to ensure container and pod IDs are globally unique.
// - nameRegistryBkt: Maps Name to ID for containers and pods.
// Used to ensure container and pod names are globally unique.
// - nsRegistryBkt: Maps ID to namespace for all containers and pods.
// Used during lookup operations to determine if a given ID is in the same
// namespace as the state.
// - ctrBkt: Contains a sub-bucket for each container in the state.
// Each sub-bucket has config and state keys holding the container's JSON
// encoded configuration and state (respectively), an optional netNS key
// containing the path to the container's network namespace, a dependencies
// bucket containing the container's dependencies, and an optional pod key
// containing the ID of the pod the container is joined to.
// - allCtrsBkt: Map of ID to name containing only containers. Used for
// container lookup operations.
// - podBkt: Contains a sub-bucket for each pod in the state.
// Each sub-bucket has config and state keys holding the pod's JSON encoded
// configuration and state, plus a containers sub bucket holding the IDs of
// containers in the pod.
// - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup
// operations.
// - runtimeConfigBkt: Contains configuration of the libpod instance that
// initially created the database. This must match for any further instances
// that access the database, to ensure that state mismatches with
// containers/storage do not occur.
// NewBoltState creates a new bolt-backed state database
func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
state := new(BoltState)
state.dbPath = path
state.lockDir = lockDir
state.runtime = runtime
state.namespace = ""
state.namespaceBytes = nil
logrus.Debugf("Initializing boltdb state at %s", path)
// Make the directory that will hold container lockfiles
if err := os.MkdirAll(lockDir, 0750); err != nil {
@ -47,6 +83,9 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
if _, err := tx.CreateBucketIfNotExists(nameRegistryBkt); err != nil {
return errors.Wrapf(err, "error creating name-registry bucket")
}
if _, err := tx.CreateBucketIfNotExists(nsRegistryBkt); err != nil {
return errors.Wrapf(err, "error creating ns-registry bucket")
}
if _, err := tx.CreateBucketIfNotExists(ctrBkt); err != nil {
return errors.Wrapf(err, "error creating containers bucket")
}
@ -193,6 +232,20 @@ func (s *BoltState) Refresh() error {
return err
}
// SetNamespace sets the namespace that will be used for container and pod
// retrieval
func (s *BoltState) SetNamespace(ns string) error {
s.namespace = ns
if ns != "" {
s.namespaceBytes = []byte(ns)
} else {
s.namespaceBytes = nil
}
return nil
}
// Container retrieves a single container from the state by its full ID
func (s *BoltState) Container(id string) (*Container, error) {
if id == "" {
@ -262,11 +315,18 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
return err
}
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
}
// First, check if the ID given was the actual container ID
var id []byte
ctrExists := ctrBucket.Bucket([]byte(idOrName))
if ctrExists != nil {
// A full container ID was given
// A full container ID was given.
// It might not be in our namespace, but
// getContainerFromDB() will handle that case.
id = []byte(idOrName)
} else {
// They did not give us a full container ID.
@ -274,6 +334,14 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
// Use else-if in case the name is set to a partial ID
exists := false
err = idBucket.ForEach(func(checkID, checkName []byte) error {
// If the container isn't in our namespace, we
// can't match it
if s.namespaceBytes != nil {
ns := nsBucket.Get(checkID)
if !bytes.Equal(ns, s.namespaceBytes) {
return nil
}
}
if string(checkName) == idOrName {
if exists {
return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
@ -332,10 +400,17 @@ func (s *BoltState) HasContainer(id string) (bool, error) {
return err
}
ctrExists := ctrBucket.Bucket(ctrID)
if ctrExists != nil {
ctrDB := ctrBucket.Bucket(ctrID)
if ctrDB != nil {
if s.namespaceBytes != nil {
nsBytes := ctrDB.Get(namespaceKey)
if bytes.Equal(nsBytes, s.namespaceBytes) {
exists = true
}
} else {
exists = true
}
}
return nil
})
@ -383,7 +458,7 @@ func (s *BoltState) RemoveContainer(ctr *Container) error {
defer db.Close()
err = db.Update(func(tx *bolt.Tx) error {
return removeContainer(ctr, nil, tx)
return s.removeContainer(ctr, nil, tx)
})
return err
}
@ -398,6 +473,10 @@ func (s *BoltState) UpdateContainer(ctr *Container) error {
return ErrCtrRemoved
}
if s.namespace != "" && s.namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
}
newState := new(containerState)
netNSPath := ""
@ -460,6 +539,10 @@ func (s *BoltState) SaveContainer(ctr *Container) error {
return ErrCtrRemoved
}
if s.namespace != "" && s.namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
}
stateJSON, err := json.Marshal(ctr.state)
if err != nil {
return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID())
@ -519,6 +602,10 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) {
return nil, ErrCtrRemoved
}
if s.namespace != "" && s.namespace != ctr.config.Namespace {
return nil, errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
}
depCtrs := []string{}
db, err := s.getDBCon()
@ -602,9 +689,20 @@ func (s *BoltState) AllContainers() ([]*Container, error) {
ctr.config = new(ContainerConfig)
ctr.state = new(containerState)
if err := s.getContainerFromDB(id, ctr, ctrBucket); err != nil {
// If the error is a namespace mismatch, we can
// ignore it safely.
// We just won't include the container in the
// results.
if errors.Cause(err) != ErrNSMismatch {
return err
}
} else {
ctrs = append(ctrs, ctr)
}
return nil
return s.getContainerFromDB(id, ctr, ctrBucket)
})
})
if err != nil {
@ -682,11 +780,18 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
return err
}
nsBkt, err := getNSBucket(tx)
if err != nil {
return err
}
// First, check if the ID given was the actual pod ID
var id []byte
podExists := podBkt.Bucket([]byte(idOrName))
if podExists != nil {
// A full pod ID was given
// A full pod ID was given.
// It might not be in our namespace, but getPodFromDB()
// will handle that case.
id = []byte(idOrName)
} else {
// They did not give us a full pod ID.
@ -694,6 +799,14 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
// Use else-if in case the name is set to a partial ID
exists := false
err = idBucket.ForEach(func(checkID, checkName []byte) error {
// If the pod isn't in our namespace, we
// can't match it
if s.namespaceBytes != nil {
ns := nsBkt.Get(checkID)
if !bytes.Equal(ns, s.namespaceBytes) {
return nil
}
}
if string(checkName) == idOrName {
if exists {
return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
@ -719,7 +832,6 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
// We might have found a container ID, but it's OK
// We'll just fail in getPodFromDB with ErrNoSuchPod
return s.getPodFromDB(id, pod, podBkt)
})
if err != nil {
@ -757,8 +869,15 @@ func (s *BoltState) HasPod(id string) (bool, error) {
podDB := podBkt.Bucket(podID)
if podDB != nil {
if s.namespaceBytes != nil {
podNS := podDB.Get(namespaceKey)
if bytes.Equal(s.namespaceBytes, podNS) {
exists = true
}
} else {
exists = true
}
}
return nil
})
@ -783,6 +902,10 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) {
return false, ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return false, errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
ctrID := []byte(id)
podID := []byte(pod.ID())
@ -813,6 +936,11 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) {
return errors.Wrapf(ErrInternal, "pod %s missing containers bucket in DB", pod.ID())
}
// Don't bother with a namespace check on the container -
// We maintain the invariant that container namespaces must
// match the namespace of the pod they join.
// We already checked the pod namespace, so we should be fine.
ctr := podCtrs.Get(ctrID)
if ctr != nil {
exists = true
@ -837,6 +965,10 @@ func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) {
return nil, ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return nil, errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
podID := []byte(pod.ID())
ctrs := []string{}
@ -895,6 +1027,10 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) {
return nil, ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return nil, errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
podID := []byte(pod.ID())
ctrs := []*Container{}
@ -961,9 +1097,18 @@ func (s *BoltState) AddPod(pod *Pod) error {
return ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
podID := []byte(pod.ID())
podName := []byte(pod.Name())
var podNamespace []byte
if pod.config.Namespace != "" {
podNamespace = []byte(pod.config.Namespace)
}
podConfigJSON, err := json.Marshal(pod.config)
if err != nil {
return errors.Wrapf(err, "error marshalling pod %s config to JSON", pod.ID())
@ -1001,6 +1146,11 @@ func (s *BoltState) AddPod(pod *Pod) error {
return err
}
nsBkt, err := getNSBucket(tx)
if err != nil {
return err
}
// Check if we already have something with the given ID and name
idExist := idsBkt.Get(podID)
if idExist != nil {
@ -1031,6 +1181,15 @@ func (s *BoltState) AddPod(pod *Pod) error {
return errors.Wrapf(err, "error storing pod %s state JSON in DB", pod.ID())
}
if podNamespace != nil {
if err := newPod.Put(namespaceKey, podNamespace); err != nil {
return errors.Wrapf(err, "error storing pod %s namespace in DB", pod.ID())
}
if err := nsBkt.Put(podID, podNamespace); err != nil {
return errors.Wrapf(err, "error storing pod %s namespace in DB", pod.ID())
}
}
// Add us to the ID and names buckets
if err := idsBkt.Put(podID, podName); err != nil {
return errors.Wrapf(err, "error storing pod %s ID in DB", pod.ID())
@ -1062,6 +1221,10 @@ func (s *BoltState) RemovePod(pod *Pod) error {
return ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
podID := []byte(pod.ID())
podName := []byte(pod.Name())
@ -1092,6 +1255,11 @@ func (s *BoltState) RemovePod(pod *Pod) error {
return err
}
nsBkt, err := getNSBucket(tx)
if err != nil {
return err
}
// Check if the pod exists
podDB := podBkt.Bucket(podID)
if podDB == nil {
@ -1120,6 +1288,9 @@ func (s *BoltState) RemovePod(pod *Pod) error {
if err := namesBkt.Delete(podName); err != nil {
return errors.Wrapf(err, "error removing pod %s name (%s) from DB", pod.ID(), pod.Name())
}
if err := nsBkt.Delete(podID); err != nil {
return errors.Wrapf(err, "error removing pod %s namespace from DB", pod.ID())
}
if err := allPodsBkt.Delete(podID); err != nil {
return errors.Wrapf(err, "error removing pod %s ID from all pods bucket in DB", pod.ID())
}
@ -1146,6 +1317,10 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error {
return ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
podID := []byte(pod.ID())
db, err := s.getDBCon()
@ -1292,6 +1467,15 @@ func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
return ErrPodRemoved
}
if s.namespace != "" {
if s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
if s.namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s in in namespace %q but we are in namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
}
}
if ctr.config.Pod == "" {
return errors.Wrapf(ErrNoSuchPod, "container %s is not part of a pod, use RemoveContainer instead", ctr.ID())
}
@ -1307,7 +1491,7 @@ func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
defer db.Close()
err = db.Update(func(tx *bolt.Tx) error {
return removeContainer(ctr, pod, tx)
return s.removeContainer(ctr, pod, tx)
})
return err
}
@ -1322,6 +1506,10 @@ func (s *BoltState) UpdatePod(pod *Pod) error {
return ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
newState := new(podState)
db, err := s.getDBCon()
@ -1375,6 +1563,10 @@ func (s *BoltState) SavePod(pod *Pod) error {
return ErrPodRemoved
}
if s.namespace != "" && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
stateJSON, err := json.Marshal(pod.state)
if err != nil {
return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID())
@ -1451,9 +1643,15 @@ func (s *BoltState) AllPods() ([]*Pod, error) {
pod.config = new(PodConfig)
pod.state = new(podState)
if err := s.getPodFromDB(id, pod, podBucket); err != nil {
if errors.Cause(err) != ErrNSMismatch {
return err
}
} else {
pods = append(pods, pod)
}
return s.getPodFromDB(id, pod, podBucket)
return nil
})
return err
})

View File

@ -1,6 +1,7 @@
package libpod
import (
"bytes"
"encoding/json"
"path/filepath"
"runtime"
@ -15,6 +16,7 @@ import (
const (
idRegistryName = "id-registry"
nameRegistryName = "name-registry"
nsRegistryName = "ns-registry"
ctrName = "ctr"
allCtrsName = "all-ctrs"
podName = "pod"
@ -27,11 +29,13 @@ const (
netNSName = "netns"
containersName = "containers"
podIDName = "pod-id"
namespaceName = "namespace"
)
var (
idRegistryBkt = []byte(idRegistryName)
nameRegistryBkt = []byte(nameRegistryName)
nsRegistryBkt = []byte(nsRegistryName)
ctrBkt = []byte(ctrName)
allCtrsBkt = []byte(allCtrsName)
podBkt = []byte(podName)
@ -44,6 +48,7 @@ var (
netNSKey = []byte(netNSName)
containersBkt = []byte(containersName)
podIDKey = []byte(podIDName)
namespaceKey = []byte(namespaceName)
)
// Check if the configuration of the database is compatible with the
@ -165,6 +170,14 @@ func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
return bkt, nil
}
func getNSBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
bkt := tx.Bucket(nsRegistryBkt)
if bkt == nil {
return nil, errors.Wrapf(ErrDBBadConfig, "namespace registry bucket not found in DB")
}
return bkt, nil
}
func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
bkt := tx.Bucket(ctrBkt)
if bkt == nil {
@ -211,6 +224,13 @@ func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error
return errors.Wrapf(ErrNoSuchPod, "pod with ID %s not found", string(id))
}
if s.namespaceBytes != nil {
podNamespaceBytes := podDB.Get(namespaceKey)
if !bytes.Equal(s.namespaceBytes, podNamespaceBytes) {
return errors.Wrapf(ErrNSMismatch, "cannot retrieve pod %s as it is part of namespace %q and we are in namespace %q", string(id), string(podNamespaceBytes), s.namespace)
}
}
podConfigBytes := podDB.Get(configKey)
if podConfigBytes == nil {
return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id))
@ -246,6 +266,11 @@ func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error
// Add a container to the DB
// If pod is not nil, the container is added to the pod as well
func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
if s.namespace != "" && s.namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "cannot add container %s as it is in namespace %q and we are in namespace %q",
ctr.ID(), s.namespace, ctr.config.Namespace)
}
// JSON container structs to insert into DB
// TODO use a higher-performance struct encoding than JSON
configJSON, err := json.Marshal(ctr.config)
@ -262,6 +287,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
ctrID := []byte(ctr.ID())
ctrName := []byte(ctr.Name())
var ctrNamespace []byte
if ctr.config.Namespace != "" {
ctrNamespace = []byte(ctr.config.Namespace)
}
db, err := s.getDBCon()
if err != nil {
return err
@ -279,6 +309,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
return err
}
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
}
ctrBucket, err := getCtrBucket(tx)
if err != nil {
return err
@ -309,6 +344,12 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
if podCtrs == nil {
return errors.Wrapf(ErrInternal, "pod %s does not have a containers bucket", pod.ID())
}
podNS := podDB.Get(namespaceKey)
if !bytes.Equal(podNS, ctrNamespace) {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and pod %s is in namespace %s",
ctr.ID(), ctr.config.Namespace, pod.ID(), pod.config.Namespace)
}
}
// Check if we already have a container with the given ID and name
@ -329,6 +370,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
if err := namesBucket.Put(ctrName, ctrID); err != nil {
return errors.Wrapf(err, "error adding container %s name (%s) to DB", ctr.ID(), ctr.Name())
}
if ctrNamespace != nil {
if err := nsBucket.Put(ctrID, ctrNamespace); err != nil {
return errors.Wrapf(err, "error adding container %s namespace (%q) to DB", ctr.ID(), ctr.Namespace())
}
}
if err := allCtrsBucket.Put(ctrID, ctrName); err != nil {
return errors.Wrapf(err, "error adding container %s to all containers bucket in DB", ctr.ID())
}
@ -344,6 +390,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
if err := newCtrBkt.Put(stateKey, stateJSON); err != nil {
return errors.Wrapf(err, "error adding container %s state to DB", ctr.ID())
}
if ctrNamespace != nil {
if err := newCtrBkt.Put(namespaceKey, ctrNamespace); err != nil {
return errors.Wrapf(err, "error adding container %s namespace to DB", ctr.ID())
}
}
if pod != nil {
if err := newCtrBkt.Put(podIDKey, []byte(pod.ID())); err != nil {
return errors.Wrapf(err, "error adding container %s pod to DB", ctr.ID())
@ -384,6 +435,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
}
}
depNamespace := depCtrBkt.Get(namespaceKey)
if !bytes.Equal(ctrNamespace, depNamespace) {
return errors.Wrapf(ErrNSMismatch, "container %s in namespace %q depends on container %s in namespace %q - namespaces must match", ctr.ID(), ctr.config.Namespace, dependsCtr, string(depNamespace))
}
depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt)
if depCtrDependsBkt == nil {
return errors.Wrapf(ErrInternal, "container %s does not have a dependencies bucket", dependsCtr)
@ -408,7 +464,7 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
// Remove a container from the DB
// If pod is not nil, the container is treated as belonging to a pod, and
// will be removed from the pod as well
func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
ctrID := []byte(ctr.ID())
ctrName := []byte(ctr.Name())
@ -427,6 +483,11 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
return err
}
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
}
allCtrsBucket, err := getAllCtrsBucket(tx)
if err != nil {
return err
@ -456,6 +517,17 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID())
}
// Compare namespace
// We can't remove containers not in our namespace
if s.namespace != "" {
if s.namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
}
if pod != nil && s.namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q, does not match out namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
}
}
if podDB != nil {
// Check if the container is in the pod, remove it if it is
podCtrs := podDB.Bucket(containersBkt)
@ -502,6 +574,9 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
if err := namesBucket.Delete(ctrName); err != nil {
return errors.Wrapf(err, "error deleting container %s name in DB", ctr.ID())
}
if err := nsBucket.Delete(ctrID); err != nil {
return errors.Wrapf(err, "error deleting container %s namespace in DB", ctr.ID())
}
if err := allCtrsBucket.Delete(ctrID); err != nil {
return errors.Wrapf(err, "error deleting container %s from all containers bucket in DB", ctr.ID())
}

View File

@ -3,6 +3,7 @@
package libpod
import (
"bytes"
"encoding/json"
"path/filepath"
@ -19,6 +20,13 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.
return errors.Wrapf(ErrNoSuchCtr, "container %s not found in DB", string(id))
}
if s.namespaceBytes != nil {
ctrNamespaceBytes := ctrBkt.Get(namespaceKey)
if !bytes.Equal(s.namespaceBytes, ctrNamespaceBytes) {
return errors.Wrapf(ErrNSMismatch, "cannot retrieve container %s as it is part of namespace %q and we are in namespace %q", string(id), string(ctrNamespaceBytes), s.namespace)
}
}
configBytes := ctrBkt.Get(configKey)
if configBytes == nil {
return errors.Wrapf(ErrInternal, "container %s missing config key in DB", string(id))

View File

@ -185,6 +185,8 @@ type ContainerConfig struct {
Name string `json:"name"`
// Full ID of the pood the container belongs to
Pod string `json:"pod,omitempty"`
// Namespace the container is in
Namespace string `json:"namespace,omitempty"`
// TODO consider breaking these subsections up into smaller structs
@ -372,6 +374,12 @@ func (c *Container) PodID() string {
return c.config.Pod
}
// Namespace returns the libpod namespace the container is in.
// Namespaces are used to logically separate containers and pods in the state.
func (c *Container) Namespace() string {
return c.config.Namespace
}
// Image returns the ID and name of the image used as the container's rootfs
func (c *Container) Image() (string, string) {
return c.config.RootfsImageID, c.config.RootfsImageName

View File

@ -69,6 +69,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data)
ImageID: config.RootfsImageID,
ImageName: config.RootfsImageName,
ExitCommand: config.ExitCommand,
Namespace: config.Namespace,
Rootfs: config.Rootfs,
ResolvConfPath: resolvPath,
HostnamePath: hostnamePath,

View File

@ -63,6 +63,10 @@ var (
// was created by a libpod with a different config
ErrDBBadConfig = errors.New("database configuration mismatch")
// ErrNSMismatch indicates that the requested pod or container is in a
// different namespace and cannot be accessed or modified.
ErrNSMismatch = errors.New("target is in a different namespace")
// ErrNotImplemented indicates that the requested functionality is not
// yet present
ErrNotImplemented = errors.New("not yet implemented")

View File

@ -14,10 +14,28 @@ import (
// An InMemoryState is a purely in-memory state store
type InMemoryState struct {
// Maps pod ID to pod struct.
pods map[string]*Pod
// Maps container ID to container struct.
containers map[string]*Container
// Maps container ID to a list of IDs of dependencies.
ctrDepends map[string][]string
// Maps pod ID to a map of container ID to container struct.
podContainers map[string]map[string]*Container
// Global name registry - ensures name uniqueness and performs lookups.
nameIndex *registrar.Registrar
// Global ID registry - ensures ID uniqueness and performs lookups.
idIndex *truncindex.TruncIndex
// Namespace the state is joined to.
namespace string
// Maps namespace name to local ID and name registries for looking up
// pods and containers in a specific namespace.
namespaceIndexes map[string]*namespaceIndex
}
// namespaceIndex contains name and ID registries for a specific namespace.
// This is used for namespaces lookup operations.
type namespaceIndex struct {
nameIndex *registrar.Registrar
idIndex *truncindex.TruncIndex
}
@ -36,6 +54,10 @@ func NewInMemoryState() (State, error) {
state.nameIndex = registrar.NewRegistrar()
state.idIndex = truncindex.NewTruncIndex([]string{})
state.namespace = ""
state.namespaceIndexes = make(map[string]*namespaceIndex)
return state, nil
}
@ -51,6 +73,13 @@ func (s *InMemoryState) Refresh() error {
return nil
}
// SetNamespace sets the namespace for container and pod retrieval.
func (s *InMemoryState) SetNamespace(ns string) error {
s.namespace = ns
return nil
}
// Container retrieves a container from its full ID
func (s *InMemoryState) Container(id string) (*Container, error) {
if id == "" {
@ -62,20 +91,43 @@ func (s *InMemoryState) Container(id string) (*Container, error) {
return nil, errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found", id)
}
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return nil, err
}
return ctr, nil
}
// LookupContainer retrieves a container by full ID, unique partial ID, or name
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
var (
nameIndex *registrar.Registrar
idIndex *truncindex.TruncIndex
)
if idOrName == "" {
return nil, ErrEmptyID
}
fullID, err := s.nameIndex.Get(idOrName)
if s.namespace != "" {
nsIndex, ok := s.namespaceIndexes[s.namespace]
if !ok {
// We have no containers in the namespace
// Return false
return nil, errors.Wrapf(ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
}
nameIndex = nsIndex.nameIndex
idIndex = nsIndex.idIndex
} else {
nameIndex = s.nameIndex
idIndex = s.idIndex
}
fullID, err := nameIndex.Get(idOrName)
if err != nil {
if err == registrar.ErrNameNotReserved {
// What was passed is not a name, assume it's an ID
fullID, err = s.idIndex.Get(idOrName)
fullID, err = idIndex.Get(idOrName)
if err != nil {
if err == truncindex.ErrNotExist {
return nil, errors.Wrapf(ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
@ -102,9 +154,12 @@ func (s *InMemoryState) HasContainer(id string) (bool, error) {
return false, ErrEmptyID
}
_, ok := s.containers[id]
ctr, ok := s.containers[id]
if !ok || (s.namespace != "" && s.namespace != ctr.config.Namespace) {
return false, nil
}
return ok, nil
return true, nil
}
// AddContainer adds a container to the state
@ -122,6 +177,10 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
return errors.Wrapf(ErrInvalidArg, "cannot add a container that is in a pod with AddContainer, use AddContainerToPod")
}
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return err
}
// There are potential race conditions with this
// But in-memory state is intended purely for testing and not production
// use, so this should be fine.
@ -133,6 +192,9 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
} else if depCtr.config.Pod != "" {
return errors.Wrapf(ErrInvalidArg, "cannot depend on container in a pod if not part of same pod")
}
if depCtr.config.Namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and cannot depend on container %s in namespace %s", ctr.ID(), ctr.config.Namespace, depID, depCtr.config.Namespace)
}
}
if err := s.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil {
@ -146,6 +208,25 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
s.containers[ctr.ID()] = ctr
// If we're in a namespace, add us to that namespace's indexes
if ctr.config.Namespace != "" {
var nsIndex *namespaceIndex
nsIndex, ok := s.namespaceIndexes[ctr.config.Namespace]
if !ok {
nsIndex = new(namespaceIndex)
nsIndex.nameIndex = registrar.NewRegistrar()
nsIndex.idIndex = truncindex.NewTruncIndex([]string{})
s.namespaceIndexes[ctr.config.Namespace] = nsIndex
}
// Should be no errors here, the previous index adds should have caught that
if err := nsIndex.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil {
return errors.Wrapf(err, "error registering container name %s", ctr.Name())
}
if err := nsIndex.idIndex.Add(ctr.ID()); err != nil {
return errors.Wrapf(err, "error registering container ID %s", ctr.ID())
}
}
// Add containers this container depends on
for _, depCtr := range depCtrs {
s.addCtrToDependsMap(ctr.ID(), depCtr)
@ -160,6 +241,10 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
// Almost no validity checks are performed, to ensure we can kick
// misbehaving containers out of the state
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return err
}
// Ensure we don't remove a container which other containers depend on
deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 {
@ -180,6 +265,17 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
delete(s.ctrDepends, ctr.ID())
if ctr.config.Namespace != "" {
nsIndex, ok := s.namespaceIndexes[ctr.config.Namespace]
if !ok {
return errors.Wrapf(ErrInternal, "error retrieving index for namespace %q", ctr.config.Namespace)
}
if err := nsIndex.idIndex.Delete(ctr.ID()); err != nil {
return errors.Wrapf(err, "error removing container %s from namespace ID index", ctr.ID())
}
nsIndex.nameIndex.Release(ctr.Name())
}
// Remove us from container dependencies
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs {
@ -204,7 +300,7 @@ func (s *InMemoryState) UpdateContainer(ctr *Container) error {
return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID())
}
return nil
return s.checkNSMatch(ctr.ID(), ctr.Namespace())
}
// SaveContainer saves a container's state
@ -223,7 +319,7 @@ func (s *InMemoryState) SaveContainer(ctr *Container) error {
return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID())
}
return nil
return s.checkNSMatch(ctr.ID(), ctr.Namespace())
}
// ContainerInUse checks if the given container is being used by other containers
@ -232,6 +328,16 @@ func (s *InMemoryState) ContainerInUse(ctr *Container) ([]string, error) {
return nil, ErrCtrRemoved
}
// If the container does not exist, return error
if _, ok := s.containers[ctr.ID()]; !ok {
ctr.valid = false
return nil, errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID())
}
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return nil, err
}
arr, ok := s.ctrDepends[ctr.ID()]
if !ok {
return []string{}, nil
@ -244,8 +350,10 @@ func (s *InMemoryState) ContainerInUse(ctr *Container) ([]string, error) {
func (s *InMemoryState) AllContainers() ([]*Container, error) {
ctrs := make([]*Container, 0, len(s.containers))
for _, ctr := range s.containers {
if s.namespace == "" || ctr.config.Namespace == s.namespace {
ctrs = append(ctrs, ctr)
}
}
return ctrs, nil
}
@ -261,21 +369,44 @@ func (s *InMemoryState) Pod(id string) (*Pod, error) {
return nil, errors.Wrapf(ErrNoSuchPod, "no pod with id %s found", id)
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return nil, err
}
return pod, nil
}
// LookupPod retrieves a pod from the state from a full or unique partial ID or
// a full name
func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) {
var (
nameIndex *registrar.Registrar
idIndex *truncindex.TruncIndex
)
if idOrName == "" {
return nil, ErrEmptyID
}
fullID, err := s.nameIndex.Get(idOrName)
if s.namespace != "" {
nsIndex, ok := s.namespaceIndexes[s.namespace]
if !ok {
// We have no containers in the namespace
// Return false
return nil, errors.Wrapf(ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
}
nameIndex = nsIndex.nameIndex
idIndex = nsIndex.idIndex
} else {
nameIndex = s.nameIndex
idIndex = s.idIndex
}
fullID, err := nameIndex.Get(idOrName)
if err != nil {
if err == registrar.ErrNameNotReserved {
// What was passed is not a name, assume it's an ID
fullID, err = s.idIndex.Get(idOrName)
fullID, err = idIndex.Get(idOrName)
if err != nil {
if err == truncindex.ErrNotExist {
return nil, errors.Wrapf(ErrNoSuchPod, "no pod found with name or ID %s", idOrName)
@ -302,9 +433,12 @@ func (s *InMemoryState) HasPod(id string) (bool, error) {
return false, ErrEmptyID
}
_, ok := s.pods[id]
pod, ok := s.pods[id]
if !ok || (s.namespace != "" && s.namespace != pod.config.Namespace) {
return false, nil
}
return ok, nil
return true, nil
}
// PodHasContainer checks if the given pod has a container with the given ID
@ -317,6 +451,10 @@ func (s *InMemoryState) PodHasContainer(pod *Pod, ctrID string) (bool, error) {
return false, ErrEmptyID
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return false, err
}
podCtrs, ok := s.podContainers[pod.ID()]
if !ok {
pod.valid = false
@ -333,6 +471,10 @@ func (s *InMemoryState) PodContainersByID(pod *Pod) ([]string, error) {
return nil, errors.Wrapf(ErrPodRemoved, "pod %s is not valid", pod.ID())
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return nil, err
}
podCtrs, ok := s.podContainers[pod.ID()]
if !ok {
pod.valid = false
@ -358,6 +500,10 @@ func (s *InMemoryState) PodContainers(pod *Pod) ([]*Container, error) {
return nil, errors.Wrapf(ErrPodRemoved, "pod %s is not valid", pod.ID())
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return nil, err
}
podCtrs, ok := s.podContainers[pod.ID()]
if !ok {
pod.valid = false
@ -383,6 +529,10 @@ func (s *InMemoryState) AddPod(pod *Pod) error {
return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and cannot be added", pod.ID())
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return err
}
if _, ok := s.pods[pod.ID()]; ok {
return errors.Wrapf(ErrPodExists, "pod with ID %s already exists in state", pod.ID())
}
@ -404,6 +554,25 @@ func (s *InMemoryState) AddPod(pod *Pod) error {
s.podContainers[pod.ID()] = make(map[string]*Container)
// If we're in a namespace, add us to that namespace's indexes
if pod.config.Namespace != "" {
var nsIndex *namespaceIndex
nsIndex, ok := s.namespaceIndexes[pod.config.Namespace]
if !ok {
nsIndex = new(namespaceIndex)
nsIndex.nameIndex = registrar.NewRegistrar()
nsIndex.idIndex = truncindex.NewTruncIndex([]string{})
s.namespaceIndexes[pod.config.Namespace] = nsIndex
}
// Should be no errors here, the previous index adds should have caught that
if err := nsIndex.nameIndex.Reserve(pod.Name(), pod.ID()); err != nil {
return errors.Wrapf(err, "error registering container name %s", pod.Name())
}
if err := nsIndex.idIndex.Add(pod.ID()); err != nil {
return errors.Wrapf(err, "error registering container ID %s", pod.ID())
}
}
return nil
}
@ -413,6 +582,10 @@ func (s *InMemoryState) RemovePod(pod *Pod) error {
// Don't make many validity checks to ensure we can kick badly formed
// pods out of the state
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return err
}
if _, ok := s.pods[pod.ID()]; !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
@ -433,6 +606,17 @@ func (s *InMemoryState) RemovePod(pod *Pod) error {
delete(s.podContainers, pod.ID())
s.nameIndex.Release(pod.Name())
if pod.config.Namespace != "" {
nsIndex, ok := s.namespaceIndexes[pod.config.Namespace]
if !ok {
return errors.Wrapf(ErrInternal, "error retrieving index for namespace %q", pod.config.Namespace)
}
if err := nsIndex.idIndex.Delete(pod.ID()); err != nil {
return errors.Wrapf(err, "error removing container %s from namespace ID index", pod.ID())
}
nsIndex.nameIndex.Release(pod.Name())
}
return nil
}
@ -445,6 +629,10 @@ func (s *InMemoryState) RemovePodContainers(pod *Pod) error {
return errors.Wrapf(ErrPodRemoved, "pod %s is not valid", pod.ID())
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return err
}
// Get pod containers
podCtrs, ok := s.podContainers[pod.ID()]
if !ok {
@ -494,6 +682,15 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrInvalidArg, "container %s is not in pod %s", ctr.ID(), pod.ID())
}
if ctr.config.Namespace != pod.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and pod %s is in namespace %s",
ctr.ID(), ctr.config.Namespace, pod.ID(), pod.config.Namespace)
}
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return err
}
// Retrieve pod containers list
podCtrs, ok := s.podContainers[pod.ID()]
if !ok {
@ -514,9 +711,13 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error {
if _, ok = s.containers[depCtr]; !ok {
return errors.Wrapf(ErrNoSuchCtr, "cannot depend on nonexistent container %s", depCtr)
}
if _, ok = podCtrs[depCtr]; !ok {
depCtrStruct, ok := podCtrs[depCtr]
if !ok {
return errors.Wrapf(ErrInvalidArg, "cannot depend on container %s as it is not in pod %s", depCtr, pod.ID())
}
if depCtrStruct.config.Namespace != ctr.config.Namespace {
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and cannot depend on container %s in namespace %s", ctr.ID(), ctr.config.Namespace, depCtr, depCtrStruct.config.Namespace)
}
}
// Add container to state
@ -538,6 +739,25 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error {
// Add container to pod containers
podCtrs[ctr.ID()] = ctr
// If we're in a namespace, add us to that namespace's indexes
if ctr.config.Namespace != "" {
var nsIndex *namespaceIndex
nsIndex, ok := s.namespaceIndexes[ctr.config.Namespace]
if !ok {
nsIndex = new(namespaceIndex)
nsIndex.nameIndex = registrar.NewRegistrar()
nsIndex.idIndex = truncindex.NewTruncIndex([]string{})
s.namespaceIndexes[ctr.config.Namespace] = nsIndex
}
// Should be no errors here, the previous index adds should have caught that
if err := nsIndex.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil {
return errors.Wrapf(err, "error registering container name %s", ctr.Name())
}
if err := nsIndex.idIndex.Add(ctr.ID()); err != nil {
return errors.Wrapf(err, "error registering container ID %s", ctr.ID())
}
}
// Add containers this container depends on
for _, depCtr := range depCtrs {
s.addCtrToDependsMap(ctr.ID(), depCtr)
@ -556,6 +776,10 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be removed from the pod", ctr.ID())
}
if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil {
return err
}
// Ensure we don't remove a container which other containers depend on
deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 {
@ -595,6 +819,17 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
// Remove the container from the pod
delete(podCtrs, ctr.ID())
if ctr.config.Namespace != "" {
nsIndex, ok := s.namespaceIndexes[ctr.config.Namespace]
if !ok {
return errors.Wrapf(ErrInternal, "error retrieving index for namespace %q", ctr.config.Namespace)
}
if err := nsIndex.idIndex.Delete(ctr.ID()); err != nil {
return errors.Wrapf(err, "error removing container %s from namespace ID index", ctr.ID())
}
nsIndex.nameIndex.Release(ctr.Name())
}
// Remove us from container dependencies
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs {
@ -611,6 +846,10 @@ func (s *InMemoryState) UpdatePod(pod *Pod) error {
return ErrPodRemoved
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return err
}
if _, ok := s.pods[pod.ID()]; !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
@ -626,6 +865,10 @@ func (s *InMemoryState) SavePod(pod *Pod) error {
return ErrPodRemoved
}
if err := s.checkNSMatch(pod.ID(), pod.Namespace()); err != nil {
return err
}
if _, ok := s.pods[pod.ID()]; !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
@ -638,8 +881,14 @@ func (s *InMemoryState) SavePod(pod *Pod) error {
func (s *InMemoryState) AllPods() ([]*Pod, error) {
pods := make([]*Pod, 0, len(s.pods))
for _, pod := range s.pods {
if s.namespace != "" {
if s.namespace == pod.config.Namespace {
pods = append(pods, pod)
}
} else {
pods = append(pods, pod)
}
}
return pods, nil
}
@ -683,3 +932,13 @@ func (s *InMemoryState) removeCtrFromDependsMap(ctrID, dependsID string) {
s.ctrDepends[dependsID] = newArr
}
}
// Check if we can access a pod or container, or if that is blocked by
// namespaces.
func (s *InMemoryState) checkNSMatch(id, ns string) error {
if s.namespace != "" && s.namespace != ns {
return errors.Wrapf(ErrNSMismatch, "cannot access %s as it is in namespace %q and we are in namespace %q",
id, ns, s.namespace)
}
return nil
}

View File

@ -284,6 +284,26 @@ func WithCNIPluginDir(dir string) RuntimeOption {
}
}
// WithNamespace sets the namespace for libpod.
// Namespaces are used to create scopes to separate containers and pods
// in the state.
// When namespace is set, libpod will only view containers and pods in
// the same namespace. All containers and pods created will default to
// the namespace set here.
// A namespace of "", the empty string, is equivalent to no namespace,
// and all containers and pods will be visible.
func WithNamespace(ns string) RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
return ErrRuntimeFinalized
}
rt.config.Namespace = ns
return nil
}
}
// Container Creation Options
// WithShmDir sets the directory that should be mounted on /dev/shm.
@ -388,8 +408,9 @@ func WithStdin() CtrCreateOption {
}
// WithPod adds the container to a pod.
// Containers which join a pod can only join the namespaces of other containers
// in the same pod.
// Containers which join a pod can only join the Linux namespaces of other
// containers in the same pod.
// Containers can only join pods in the same libpod namespace.
func (r *Runtime) WithPod(pod *Pod) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
@ -944,7 +965,8 @@ func WithCommand(command []string) CtrCreateOption {
}
}
// WithRootFS sets the rootfs for the container
// WithRootFS sets the rootfs for the container.
// This creates a container from a directory on disk and not an image.
func WithRootFS(rootfs string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
@ -961,6 +983,22 @@ func WithRootFS(rootfs string) CtrCreateOption {
}
}
// WithCtrNamespace sets the namespace the container will be created in.
// Namespaces are used to create separate views of Podman's state - runtimes can
// join a specific namespace and see only containers and pods in that namespace.
// Empty string namespaces are allowed, and correspond to a lack of namespace.
func WithCtrNamespace(ns string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return ErrCtrFinalized
}
ctr.config.Namespace = ns
return nil
}
}
// Pod Creation Options
// WithPodName sets the name of the pod.
@ -1025,3 +1063,20 @@ func WithPodCgroups() PodCreateOption {
return nil
}
}
// WithPodNamespace sets the namespace for the created pod.
// Namespaces are used to create separate views of Podman's state - runtimes can
// join a specific namespace and see only containers and pods in that namespace.
// Empty string namespaces are allowed, and correspond to a lack of namespace.
// Containers must belong to the same namespace as the pod they join.
func WithPodNamespace(ns string) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return ErrPodFinalized
}
pod.config.Namespace = ns
return nil
}
}

View File

@ -27,6 +27,8 @@ type Pod struct {
type PodConfig struct {
ID string `json:"id"`
Name string `json:"name"`
// Namespace the pod is in
Namespace string `json:"namespace,omitempty"`
// Labels contains labels applied to the pod
Labels map[string]string `json:"labels"`
@ -58,6 +60,12 @@ func (p *Pod) Name() string {
return p.config.Name
}
// Namespace returns the pod's libpod namespace.
// Namespaces are used to logically separate containers and pods in the state.
func (p *Pod) Namespace() string {
return p.config.Namespace
}
// Labels returns the pod's labels
func (p *Pod) Labels() map[string]string {
labels := make(map[string]string)

View File

@ -136,10 +136,22 @@ type RuntimeConfig struct {
// CNIDefaultNetwork is the network name of the default CNI network
// to attach pods to
CNIDefaultNetwork string `toml:"cni_default_network,omitempty"`
// HooksDirNotExistFatal switches between fatal errors and non-fatal warnings if the configured HooksDir does not exist.
// HooksDirNotExistFatal switches between fatal errors and non-fatal
// warnings if the configured HooksDir does not exist.
HooksDirNotExistFatal bool `toml:"hooks_dir_not_exist_fatal"`
// DefaultMountsFile is the path to the default mounts file for testing purposes only
// DefaultMountsFile is the path to the default mounts file for testing
// purposes only
DefaultMountsFile string `toml:"-"`
// Namespace is the libpod namespace to use.
// Namespaces are used to create scopes to separate containers and pods
// in the state.
// When namespace is set, libpod will only view containers and pods in
// the same namespace. All containers and pods created will default to
// the namespace set here.
// A namespace of "", the empty string, is equivalent to no namespace,
// and all containers and pods will be visible.
// The default namespace is "".
Namespace string `toml:"namespace,omitempty"`
}
var (
@ -493,6 +505,11 @@ func makeRuntime(runtime *Runtime) (err error) {
return errors.Wrapf(ErrInvalidArg, "unrecognized state type passed")
}
if err := runtime.state.SetNamespace(runtime.config.Namespace); err != nil {
return errors.Wrapf(err, "error setting libpod namespace in state")
}
logrus.Debugf("Set libpod namespace to %q", runtime.config.Namespace)
// We now need to see if the system has restarted
// We check for the presence of a file in our tmp directory to verify this
// This check must be locked to prevent races

View File

@ -42,6 +42,12 @@ func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, options ..
}
ctr.config.StopTimeout = CtrRemoveTimeout
// Set namespace based on current runtime namespace
// Do so before options run so they can override it
if r.config.Namespace != "" {
ctr.config.Namespace = r.config.Namespace
}
for _, option := range options {
if err := option(ctr); err != nil {
return nil, errors.Wrapf(err, "error running container create option")

View File

@ -27,6 +27,12 @@ func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) {
return nil, errors.Wrapf(err, "error creating pod")
}
// Set default namespace to runtime's namespace
// Do so before options run so they can override it
if r.config.Namespace != "" {
pod.config.Namespace = r.config.Namespace
}
for _, option := range options {
if err := option(pod); err != nil {
return nil, errors.Wrapf(err, "error running pod create option")

View File

@ -9,67 +9,110 @@ type State interface {
// Refresh clears container and pod states after a reboot
Refresh() error
// Return a container from the database from its full ID
// SetNamespace() sets the namespace for the store, and will determine
// what containers are retrieved with container and pod retrieval calls.
// A namespace of "", the empty string, acts as no namespace, and
// containers and pods in all namespaces will be returned.
SetNamespace(ns string) error
// Return a container from the database from its full ID.
// If the container is not in the set namespace, an error will be
// returned.
Container(id string) (*Container, error)
// Return a container from the database by full or partial ID or full
// name
// name.
// Containers not in the set namespace will be ignored.
LookupContainer(idOrName string) (*Container, error)
// Check if a container with the given full ID exists in the database
// Check if a container with the given full ID exists in the database.
// If the container exists but is not in the set namespace, false will
// be returned.
HasContainer(id string) (bool, error)
// Adds container to state
// The container cannot be part of a pod
// Adds container to state.
// The container cannot be part of a pod.
// The container must have globally unique name and ID - pod names and
// IDs also conflict with container names and IDs
// IDs also conflict with container names and IDs.
// The container must be in the set namespace if a namespace has been
// set.
// All containers this container depends on must be part of the same
// namespace and must not be joined to a pod.
AddContainer(ctr *Container) error
// Removes container from state
// Containers that are part of pods must use RemoveContainerFromPod
// Removes container from state.
// Containers that are part of pods must use RemoveContainerFromPod.
// The container must be part of the set namespace.
RemoveContainer(ctr *Container) error
// UpdateContainer updates a container's state from the backing store
// UpdateContainer updates a container's state from the backing store.
// The container must be part of the set namespace.
UpdateContainer(ctr *Container) error
// SaveContainer saves a container's current state to the backing store
// SaveContainer saves a container's current state to the backing store.
// The container must be part of the set namespace.
SaveContainer(ctr *Container) error
// ContainerInUse checks if other containers depend upon a given
// container
// container.
// It returns a slice of the IDs of containers which depend on the given
// container. If the slice is empty, no container depend on the given
// container.
// A container cannot be removed if other containers depend on it
// A container cannot be removed if other containers depend on it.
// The container being checked must be part of the set namespace.
ContainerInUse(ctr *Container) ([]string, error)
// Retrieves all containers presently in state
// Retrieves all containers presently in state.
// If a namespace is set, only containers within the namespace will be
// returned.
AllContainers() ([]*Container, error)
// Accepts full ID of pod
// Accepts full ID of pod.
// If the pod given is not in the set namespace, an error will be
// returned.
Pod(id string) (*Pod, error)
// Accepts full or partial IDs (as long as they are unique) and names
// Accepts full or partial IDs (as long as they are unique) and names.
// Pods not in the set namespace are ignored.
LookupPod(idOrName string) (*Pod, error)
// Checks if a pod with the given ID is present in the state
// Checks if a pod with the given ID is present in the state.
// If the given pod is not in the set namespace, false is returned.
HasPod(id string) (bool, error)
// Check if a pod has a container with the given ID
// Check if a pod has a container with the given ID.
// The pod must be part of the set namespace.
PodHasContainer(pod *Pod, ctrID string) (bool, error)
// Get the IDs of all containers in a pod
// Get the IDs of all containers in a pod.
// The pod must be part of the set namespace.
PodContainersByID(pod *Pod) ([]string, error)
// Get all the containers in a pod
// Get all the containers in a pod.
// The pod must be part of the set namespace.
PodContainers(pod *Pod) ([]*Container, error)
// Adds pod to state
// Adds pod to state.
// The pod must be part of the set namespace.
// The pod's name and ID must be globally unique.
AddPod(pod *Pod) error
// Removes pod from state
// Only empty pods can be removed from the state
// Removes pod from state.
// Only empty pods can be removed from the state.
// The pod must be part of the set namespace.
RemovePod(pod *Pod) error
// Remove all containers from a pod
// Remove all containers from a pod.
// Used to simultaneously remove containers that might otherwise have
// dependency issues
// Will fail if a dependency outside the pod is encountered
// dependency issues.
// Will fail if a dependency outside the pod is encountered.
// The pod must be part of the set namespace.
RemovePodContainers(pod *Pod) error
// AddContainerToPod adds a container to an existing pod
// The container given will be added to the state and the pod
// AddContainerToPod adds a container to an existing pod.
// The container given will be added to the state and the pod.
// The container and its dependencies must be part of the given pod,
// and the given pod's namespace.
// The pod must be part of the set namespace.
// The pod must already exist in the state.
// The container's name and ID must be globally unique.
AddContainerToPod(pod *Pod, ctr *Container) error
// RemoveContainerFromPod removes a container from an existing pod
// The container will also be removed from the state
// RemoveContainerFromPod removes a container from an existing pod.
// The container will also be removed from the state.
// The container must be in the given pod, and the pod must be in the
// set namespace.
RemoveContainerFromPod(pod *Pod, ctr *Container) error
// UpdatePod updates a pod's state from the database
// UpdatePod updates a pod's state from the database.
// The pod must be in the set namespace.
UpdatePod(pod *Pod) error
// SavePod saves a pod's state to the database
// SavePod saves a pod's state to the database.
// The pod must be in the set namespace.
SavePod(pod *Pod) error
// Retrieves all pods presently in state
// Retrieves all pods presently in state.
// If a namespace has been set, only pods in that namespace will be
// returned.
AllPods() ([]*Pod, error)
}

File diff suppressed because it is too large Load Diff

View File

@ -169,6 +169,7 @@ type ContainerInspectData struct {
Dependencies []string `json:"Dependencies"`
NetworkSettings *NetworkSettings `json:"NetworkSettings"` //TODO
ExitCommand []string `json:"ExitCommand"`
Namespace string `json:"Namespace"`
}
// ContainerInspectState represents the state of a container.

View File

@ -0,0 +1,51 @@
package integration
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman namespaces", func() {
var (
tempdir string
err error
podmanTest PodmanTest
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanCreate(tempdir)
podmanTest.RestoreAllArtifacts()
})
AfterEach(func() {
podmanTest.Cleanup()
})
It("podman namespace test", func() {
podman1 := podmanTest.Podman([]string{"--namespace", "test1", "run", "-d", ALPINE, "echo", "hello"})
podman1.WaitWithDefaultTimeout()
Expect(podman1.ExitCode()).To(Equal(0))
podman2 := podmanTest.Podman([]string{"--namespace", "test2", "ps", "-aq"})
podman2.WaitWithDefaultTimeout()
Expect(podman2.ExitCode()).To(Equal(0))
output := podman2.OutputToStringArray()
numCtrs := 0
for _, outputLine := range output {
if outputLine != "" {
numCtrs = numCtrs + 1
}
}
Expect(numCtrs).To(Equal(0))
numberOfCtrsNoNamespace := podmanTest.NumberOfContainers()
Expect(numberOfCtrsNoNamespace).To(Equal(1))
})
})