diff options
author | Daniel J Walsh <dwalsh@redhat.com> | 2018-07-25 08:47:35 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-25 08:47:35 -0400 |
commit | c90b7400a8b9ffc77de69ad3aae1754ac006ba21 (patch) | |
tree | 80f5a45ff74c22d8571710df1febd7b25f5b3aaa | |
parent | 32b690e90298f3c17e71c08d87e9727cfce8d3fb (diff) | |
parent | 1b51e88098e0c77cddd8de3484ef56965352bcf3 (diff) | |
download | podman-c90b7400a8b9ffc77de69ad3aae1754ac006ba21.tar.gz podman-c90b7400a8b9ffc77de69ad3aae1754ac006ba21.tar.bz2 podman-c90b7400a8b9ffc77de69ad3aae1754ac006ba21.zip |
Merge pull request #1116 from mheon/namespaces
Add Pod and Container namespaces
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 4 | ||||
-rw-r--r-- | cmd/podman/main.go | 5 | ||||
-rw-r--r-- | completions/bash/podman | 1 | ||||
-rw-r--r-- | docs/podman.1.md | 7 | ||||
-rw-r--r-- | libpod.conf | 8 | ||||
-rw-r--r-- | libpod/boltdb_state.go | 232 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 77 | ||||
-rw-r--r-- | libpod/boltdb_state_linux.go | 8 | ||||
-rw-r--r-- | libpod/container.go | 8 | ||||
-rw-r--r-- | libpod/container_inspect.go | 1 | ||||
-rw-r--r-- | libpod/errors.go | 4 | ||||
-rw-r--r-- | libpod/in_memory_state.go | 295 | ||||
-rw-r--r-- | libpod/options.go | 61 | ||||
-rw-r--r-- | libpod/pod.go | 8 | ||||
-rw-r--r-- | libpod/runtime.go | 21 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 6 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 6 | ||||
-rw-r--r-- | libpod/state.go | 107 | ||||
-rw-r--r-- | libpod/state_test.go | 1065 | ||||
-rw-r--r-- | pkg/inspect/inspect.go | 1 | ||||
-rw-r--r-- | test/e2e/namespace_test.go | 51 |
21 files changed, 1901 insertions, 75 deletions
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 3216d288b..9d1347cc5 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -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"))) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 3dbf196c2..dbd7c1155 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -173,6 +173,11 @@ func main() { 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", }, diff --git a/completions/bash/podman b/completions/bash/podman index 11203e52d..5d20bab3e 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2224,6 +2224,7 @@ _podman_podman() { --storage-driver --storage-opt --log-level + --namespace " local boolean_options=" --help -h diff --git a/docs/podman.1.md b/docs/podman.1.md index 5581e0569..1ca420d12 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -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** diff --git a/libpod.conf b/libpod.conf index da5e010f2..3a363e256 100644 --- a/libpod.conf +++ b/libpod.conf @@ -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 = "" diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 45d09348e..afbaecffb 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -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 - lockDir string - runtime *Runtime + 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,9 +400,16 @@ func (s *BoltState) HasContainer(id string) (bool, error) { return err } - ctrExists := ctrBucket.Bucket(ctrID) - if ctrExists != nil { - exists = true + 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) - ctrs = append(ctrs, ctr) + 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,7 +869,14 @@ func (s *BoltState) HasPod(id string) (bool, error) { podDB := podBkt.Bucket(podID) if podDB != nil { - exists = true + 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) - pods = append(pods, pod) + 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 }) diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 69e7bee21..b03c11531 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -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()) } diff --git a/libpod/boltdb_state_linux.go b/libpod/boltdb_state_linux.go index ceea955bd..7c0e08fb3 100644 --- a/libpod/boltdb_state_linux.go +++ b/libpod/boltdb_state_linux.go @@ -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)) diff --git a/libpod/container.go b/libpod/container.go index b4a1eeb12..456fc412d 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -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 diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index a1070cf99..dec0b47b4 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -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, diff --git a/libpod/errors.go b/libpod/errors.go index ddd586e29..75b4928da 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -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") diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 36077b9d1..8bdd0881c 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -14,12 +14,30 @@ import ( // An InMemoryState is a purely in-memory state store type InMemoryState struct { - pods map[string]*Pod - containers map[string]*Container - ctrDepends map[string][]string + // 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 - nameIndex *registrar.Registrar - idIndex *truncindex.TruncIndex + // 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 } // NewInMemoryState initializes a new, empty in-memory state @@ -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,7 +350,9 @@ 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 { - ctrs = append(ctrs, ctr) + 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,7 +881,13 @@ 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 { - pods = append(pods, pod) + 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 +} diff --git a/libpod/options.go b/libpod/options.go index 718b44930..7bb4a3632 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -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 + } +} diff --git a/libpod/pod.go b/libpod/pod.go index fb69787ed..a5b87f8b5 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -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) diff --git a/libpod/runtime.go b/libpod/runtime.go index a551c9134..1a384fde2 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -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 diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 82a2fed19..709775e4a 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -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") diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index 25340abdb..3355e4322 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -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") diff --git a/libpod/state.go b/libpod/state.go index b71f811ea..1b82349b3 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -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) } diff --git a/libpod/state_test.go b/libpod/state_test.go index 4d5eb9713..30638024c 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -281,6 +281,95 @@ func TestAddCtrDepInPodFails(t *testing.T) { }) } +func TestAddCtrDepInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + testCtr1.config.Namespace = "test1" + testCtr2.config.Namespace = "test1" + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 2, len(ctrs)) + }) +} + +func TestAddCtrDepInDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + testCtr1.config.Namespace = "test1" + testCtr2.config.Namespace = "test2" + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + testContainersEqual(t, testCtr1, ctrs[0]) + }) +} + +func TestAddCtrSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + state.SetNamespace("test1") + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, retrievedCtr) + }) +} + +func TestAddCtrDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + state.SetNamespace("test2") + + err = state.AddContainer(testCtr) + assert.Error(t, err) + + state.SetNamespace("") + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) + }) +} + func TestGetNonexistentContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.Container("does not exist") @@ -295,6 +384,59 @@ func TestGetContainerWithEmptyIDFails(t *testing.T) { }) } +func TestGetContainerInDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test2" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + _, err = state.Container(testCtr.ID()) + assert.Error(t, err) + }) +} + +func TestGetContainerInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + ctr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, ctr) + }) +} + +func TestGetContainerInNamespaceWhileNotInNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, ctr) + }) +} + func TestLookupContainerWithEmptyIDFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.LookupContainer("") @@ -398,6 +540,66 @@ func TestLookupCtrByPodIDFails(t *testing.T) { }) } +func TestLookupCtrInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + ctr, err := state.LookupContainer(testCtr.ID()) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, ctr) + }) +} + +func TestLookupCtrInDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.LookupContainer(testCtr.ID()) + assert.Error(t, err) + }) +} + +func TestLookupContainerMatchInDifferentNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer(strings.Repeat("0", 32), "test1", lockPath) + assert.NoError(t, err) + testCtr1.config.Namespace = "test2" + testCtr2, err := getTestContainer(strings.Repeat("0", 31)+"1", "test2", lockPath) + assert.NoError(t, err) + testCtr2.config.Namespace = "test1" + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + state.SetNamespace("test1") + + ctr, err := state.LookupContainer("000") + assert.NoError(t, err) + + testContainersEqual(t, testCtr2, ctr) + }) +} + func TestHasContainerEmptyIDFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.HasContainer("") @@ -441,6 +643,42 @@ func TestHasContainerPodIDIsFalse(t *testing.T) { }) } +func TestHasContainerSameNamespaceIsTrue(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + exists, err := state.HasContainer(testCtr.ID()) + assert.NoError(t, err) + assert.True(t, exists) + }) +} + +func TestHasContainerDifferentNamespaceIsFalse(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + exists, err := state.HasContainer(testCtr.ID()) + assert.NoError(t, err) + assert.False(t, exists) + }) +} + func TestSaveAndUpdateContainer(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) @@ -466,6 +704,35 @@ func TestSaveAndUpdateContainer(t *testing.T) { }) } +func TestSaveAndUpdateContainerSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + retrievedCtr.state.State = ContainerStateStopped + retrievedCtr.state.ExitCode = 127 + retrievedCtr.state.FinishedTime = time.Now() + + err = state.SaveContainer(retrievedCtr) + assert.NoError(t, err) + + err = state.UpdateContainer(testCtr) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, retrievedCtr) + }) +} + func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) @@ -484,6 +751,23 @@ func TestUpdateInvalidContainerReturnsError(t *testing.T) { }) } +func TestUpdateContainerNotInNamespaceReturnsError(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.UpdateContainer(testCtr) + assert.Error(t, err) + }) +} + func TestSaveInvalidContainerReturnsError(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.SaveContainer(&Container{config: &ContainerConfig{ID: "1234"}}) @@ -502,6 +786,23 @@ func TestSaveContainerNotInStateReturnsError(t *testing.T) { }) } +func TestSaveContainerNotInNamespaceReturnsError(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.SaveContainer(testCtr) + assert.Error(t, err) + }) +} + func TestRemoveContainer(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) @@ -534,6 +835,33 @@ func TestRemoveNonexistantContainerFails(t *testing.T) { }) } +func TestRemoveContainerNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + state.SetNamespace("test2") + + err = state.RemoveContainer(testCtr) + assert.Error(t, err) + + state.SetNamespace("") + + ctrs2, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs2)) + }) +} + func TestGetAllContainersOnNewStateIsEmpty(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { ctrs, err := state.AllContainers() @@ -577,6 +905,50 @@ func TestGetAllContainersTwoContainers(t *testing.T) { }) } +func TestGetAllContainersNoContainerInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) + }) +} + +func TestGetContainerOneContainerInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr1.config.Namespace = "test1" + + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + state.SetNamespace("test1") + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + testContainersEqual(t, testCtr1, ctrs[0]) + }) +} + func TestContainerInUseInvalidContainer(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.ContainerInUse(&Container{}) @@ -584,6 +956,32 @@ func TestContainerInUseInvalidContainer(t *testing.T) { }) } +func TestContainerInUseCtrNotInState(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + _, err = state.ContainerInUse(testCtr) + assert.Error(t, err) + }) +} + +func TestContainerInUseCtrNotInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.ContainerInUse(testCtr) + assert.Error(t, err) + }) +} + func TestContainerInUseOneContainer(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr1, err := getTestCtr1(lockPath) @@ -972,6 +1370,42 @@ func TestGetPodByCtrID(t *testing.T) { }) } +func TestGetPodInNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test1") + + statePod, err := state.Pod(testPod.ID()) + assert.NoError(t, err) + + testPodsEqual(t, testPod, statePod) + }) +} + +func TestGetPodPodNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.Pod(testPod.ID()) + assert.Error(t, err) + }) +} + func TestLookupPodEmptyID(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.LookupPod("") @@ -1076,6 +1510,69 @@ func TestLookupPodByCtrName(t *testing.T) { }) } +func TestLookupPodInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test1") + + statePod, err := state.LookupPod(testPod.ID()) + assert.NoError(t, err) + + testPodsEqual(t, testPod, statePod) + }) +} + +func TestLookupPodInDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.LookupPod(testPod.ID()) + assert.Error(t, err) + }) +} + +func TestLookupPodOneInDifferentNamespaceFindsRightPod(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod1, err := getTestPod(strings.Repeat("1", 32), "test1", lockPath) + assert.NoError(t, err) + + testPod1.config.Namespace = "test1" + + testPod2, err := getTestPod(strings.Repeat("1", 31)+"2", "test2", lockPath) + assert.NoError(t, err) + + testPod2.config.Namespace = "test2" + + err = state.AddPod(testPod1) + assert.NoError(t, err) + + err = state.AddPod(testPod2) + assert.NoError(t, err) + + state.SetNamespace("test1") + + pod, err := state.LookupPod(strings.Repeat("1", 5)) + assert.NoError(t, err) + + testPodsEqual(t, testPod1, pod) + }) +} + func TestHasPodEmptyIDErrors(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.HasPod("") @@ -1133,6 +1630,42 @@ func TestHasPodCtrIDFalse(t *testing.T) { }) } +func TestHasPodSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test1") + + exist, err := state.HasPod(testPod.ID()) + assert.NoError(t, err) + assert.True(t, exist) + }) +} + +func TestHasPodDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + exist, err := state.HasPod(testPod.ID()) + assert.NoError(t, err) + assert.False(t, exist) + }) +} + func TestAddPodInvalidPodErrors(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.AddPod(&Pod{config: &PodConfig{}}) @@ -1257,6 +1790,47 @@ func TestAddPodCtrNameConflictFails(t *testing.T) { }) } +func TestAddPodSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + state.SetNamespace("test1") + + err = state.AddPod(testPod) + assert.NoError(t, err) + + allPods, err := state.AllPods() + assert.NoError(t, err) + assert.Equal(t, 1, len(allPods)) + + testPodsEqual(t, testPod, allPods[0]) + assert.Equal(t, testPod.valid, allPods[0].valid) + }) +} + +func TestAddPodDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + state.SetNamespace("test2") + + err = state.AddPod(testPod) + assert.Error(t, err) + + state.SetNamespace("") + + allPods, err := state.AllPods() + assert.NoError(t, err) + assert.Equal(t, 0, len(allPods)) + }) +} + func TestRemovePodInvalidPodErrors(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.RemovePod(&Pod{config: &PodConfig{}}) @@ -1368,6 +1942,29 @@ func TestRemovePodAfterEmptySucceeds(t *testing.T) { }) } +func TestRemovePodNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.RemovePod(testPod) + assert.Error(t, err) + + state.SetNamespace("") + + allPods, err := state.AllPods() + assert.NoError(t, err) + assert.Equal(t, 1, len(allPods)) + }) +} + func TestAllPodsEmptyOnEmptyState(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { allPods, err := state.AllPods() @@ -1430,6 +2027,52 @@ func TestAllPodsMultiplePods(t *testing.T) { }) } +func TestAllPodsPodInDifferentNamespaces(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + allPods, err := state.AllPods() + assert.NoError(t, err) + assert.Equal(t, 0, len(allPods)) + }) +} + +func TestAllPodsOnePodInDifferentNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod1, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod1.config.Namespace = "test1" + + testPod2, err := getTestPod2(lockPath) + assert.NoError(t, err) + + testPod2.config.Namespace = "test2" + + err = state.AddPod(testPod1) + assert.NoError(t, err) + + err = state.AddPod(testPod2) + assert.NoError(t, err) + + state.SetNamespace("test1") + + allPods, err := state.AllPods() + assert.NoError(t, err) + assert.Equal(t, 1, len(allPods)) + + testPodsEqual(t, testPod1, allPods[0]) + }) +} + func TestPodHasContainerNoSuchPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.PodHasContainer(&Pod{config: &PodConfig{}}, strings.Repeat("0", 32)) @@ -1506,6 +2149,23 @@ func TestPodHasContainerSucceeds(t *testing.T) { }) } +func TestPodHasContainerPodNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.PodHasContainer(testPod, strings.Repeat("2", 32)) + assert.Error(t, err) + }) +} + func TestPodContainersByIDInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.PodContainersByID(&Pod{config: &PodConfig{}}) @@ -1608,6 +2268,23 @@ func TestPodContainersByIDMultipleContainers(t *testing.T) { }) } +func TestPodContainerByIDPodNotInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.PodContainersByID(testPod) + assert.Error(t, err) + }) +} + func TestPodContainersInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.PodContainers(&Pod{config: &PodConfig{}}) @@ -1615,7 +2292,7 @@ func TestPodContainersInvalidPod(t *testing.T) { }) } -func TestPodContainerdPodNotInState(t *testing.T) { +func TestPodContainersPodNotInState(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testPod, err := getTestPod1(lockPath) assert.NoError(t, err) @@ -1711,6 +2388,23 @@ func TestPodContainersMultipleContainers(t *testing.T) { }) } +func TestPodContainersPodNotInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + _, err = state.PodContainers(testPod) + assert.Error(t, err) + }) +} + func TestRemovePodContainersInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.RemovePodContainers(&Pod{config: &PodConfig{}}) @@ -1867,6 +2561,23 @@ func TestRemovePodContainerDependencyInPod(t *testing.T) { }) } +func TestRemoveContainersNotInNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.RemovePodContainers(testPod) + assert.Error(t, err) + }) +} + func TestAddContainerToPodInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) @@ -2224,6 +2935,225 @@ func TestAddContainerToPodDependencyOutsidePodFails(t *testing.T) { }) } +func TestAddContainerToPodDependencyInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr1, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr1.config.Pod = testPod.ID() + testCtr1.config.Namespace = "test1" + + testCtr2, err := getTestCtrN("3", lockPath) + assert.NoError(t, err) + testCtr2.config.Pod = testPod.ID() + testCtr2.config.IPCNsCtr = testCtr1.ID() + testCtr2.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr1) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr2) + assert.NoError(t, err) + + deps, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(deps)) + assert.Equal(t, testCtr2.ID(), deps[0]) + }) +} + +func TestAddContainerToPodDependencyInSeparateNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr1, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr1.config.Pod = testPod.ID() + testCtr1.config.Namespace = "test1" + + testCtr2, err := getTestCtrN("3", lockPath) + assert.NoError(t, err) + testCtr2.config.Pod = testPod.ID() + testCtr2.config.IPCNsCtr = testCtr1.ID() + testCtr2.config.Namespace = "test2" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr1) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr2) + assert.Error(t, err) + + ctrs, err := state.PodContainers(testPod) + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(allCtrs)) + + deps, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 0, len(deps)) + }) +} + +func TestAddContainerToPodSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Namespace = "test1" + testCtr.config.Pod = testPod.ID() + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.NoError(t, err) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(allCtrs)) + testContainersEqual(t, testCtr, allCtrs[0]) + }) +} + +func TestAddContainerToPodDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Namespace = "test2" + testCtr.config.Pod = testPod.ID() + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.Error(t, err) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(allCtrs)) + }) +} + +func TestAddContainerToPodNamespaceOnCtrFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Namespace = "test1" + testCtr.config.Pod = testPod.ID() + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.Error(t, err) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(allCtrs)) + }) +} + +func TestAddContainerToPodNamespaceOnPodFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Pod = testPod.ID() + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.Error(t, err) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(allCtrs)) + }) +} + +func TestAddCtrToPodSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testPod, err := getTestPod2(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + testPod.config.Namespace = "test1" + testCtr.config.Pod = testPod.ID() + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test1") + + err = state.AddContainerToPod(testPod, testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + testContainersEqual(t, testCtr, retrievedCtr) + }) +} + +func TestAddCtrToPodDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testPod, err := getTestPod2(lockPath) + assert.NoError(t, err) + + testCtr.config.Namespace = "test1" + testPod.config.Namespace = "test1" + testCtr.config.Pod = testPod.ID() + + state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.AddContainerToPod(testPod, testCtr) + assert.Error(t, err) + + state.SetNamespace("") + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) + }) +} + func TestRemoveContainerFromPodBadPodFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) @@ -2397,6 +3327,76 @@ func TestRemoveContainerFromPodWithDependencySucceedsAfterDepRemoved(t *testing. }) } +func TestRemoveContainerFromPodSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Pod = testPod.ID() + + testCtr.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.NoError(t, err) + + state.SetNamespace("test1") + + err = state.RemoveContainerFromPod(testPod, testCtr) + assert.NoError(t, err) + + ctrs, err := state.PodContainers(testPod) + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(allCtrs)) + }) +} + +func TestRemoveContainerFromPodDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + testCtr, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr.config.Pod = testPod.ID() + + testCtr.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.RemoveContainerFromPod(testPod, testCtr) + assert.Error(t, err) + + state.SetNamespace("") + + ctrs, err := state.PodContainers(testPod) + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(allCtrs)) + }) +} + func TestUpdatePodInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.UpdatePod(&Pod{config: &PodConfig{}}) @@ -2414,6 +3414,23 @@ func TestUpdatePodPodNotInStateFails(t *testing.T) { }) } +func TestUpdatePodNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.UpdatePod(testPod) + assert.Error(t, err) + }) +} + func TestSavePodInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { err := state.SavePod(&Pod{config: &PodConfig{}}) @@ -2431,6 +3448,23 @@ func TestSavePodPodNotInStateFails(t *testing.T) { }) } +func TestSavePodNotInNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test2") + + err = state.SavePod(testPod) + assert.Error(t, err) + }) +} + func TestSaveAndUpdatePod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testPod, err := getTestPod1(lockPath) @@ -2455,3 +3489,32 @@ func TestSaveAndUpdatePod(t *testing.T) { testPodsEqual(t, testPod, statePod) }) } + +func TestSaveAndUpdatePodSameNamespace(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + state.SetNamespace("test1") + + statePod, err := state.Pod(testPod.ID()) + assert.NoError(t, err) + + testPodsEqual(t, testPod, statePod) + + testPod.state.CgroupPath = "/new/path/for/test" + + err = state.SavePod(testPod) + assert.NoError(t, err) + + err = state.UpdatePod(statePod) + assert.NoError(t, err) + + testPodsEqual(t, testPod, statePod) + }) +} diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 5b5a27c3d..d2c9e79a5 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -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. diff --git a/test/e2e/namespace_test.go b/test/e2e/namespace_test.go new file mode 100644 index 000000000..7cc6dc114 --- /dev/null +++ b/test/e2e/namespace_test.go @@ -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)) + }) +}) |