From 24457873366bbd23d71b364a63037f34c652c04a Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 25 Jun 2018 10:35:15 -0400 Subject: Add container and pod namespaces to configs Libpod namespaces are a way to logically separate groups of pods and containers within the state. Signed-off-by: Matthew Heon --- libpod/container.go | 8 ++++++++ libpod/options.go | 41 ++++++++++++++++++++++++++++++++++++++--- libpod/pod.go | 8 ++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) (limited to 'libpod') 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/options.go b/libpod/options.go index 718b44930..fb07d1edf 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -388,8 +388,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 +945,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 +963,22 @@ func WithRootFS(rootfs string) CtrCreateOption { } } +// WithNamespace 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 WithNamespace(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 +1043,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) -- cgit v1.2.3-54-g00ecf From ab9bc2187795b61a41dfa825ddf173ff92d531d1 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 25 Jun 2018 13:27:57 -0400 Subject: Add namespaces and initial constraints to database Add basic awareness of namespaces to the database. As part of this, add constraints so containers can only be added to pods in the same namespace. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 11 +++++ libpod/boltdb_state_internal.go | 19 +++++++++ libpod/errors.go | 4 ++ libpod/in_memory_state.go | 5 +++ libpod/state_test.go | 91 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 45d09348e..648c14267 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -964,6 +964,11 @@ func (s *BoltState) AddPod(pod *Pod) error { 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()) @@ -1031,6 +1036,12 @@ 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()) + } + } + // 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()) diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 69e7bee21..5661c5b7f 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" @@ -27,6 +28,7 @@ const ( netNSName = "netns" containersName = "containers" podIDName = "pod-id" + namespaceName = "namespace" ) var ( @@ -44,6 +46,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 @@ -262,6 +265,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 @@ -309,6 +317,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 @@ -344,6 +358,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()) 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..cf2f43477 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -494,6 +494,11 @@ 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) + } + // Retrieve pod containers list podCtrs, ok := s.podContainers[pod.ID()] if !ok { diff --git a/libpod/state_test.go b/libpod/state_test.go index 4d5eb9713..7174bbf2a 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -2224,6 +2224,97 @@ func TestAddContainerToPodDependencyOutsidePodFails(t *testing.T) { }) } +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 TestRemoveContainerFromPodBadPodFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr, err := getTestCtr1(lockPath) -- cgit v1.2.3-54-g00ecf From e838dcb4bf7dc35b1bcf21edad6a1f6c59d969ab Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 25 Jun 2018 23:39:11 -0400 Subject: Add constraint that dependencies must be in the same ns Dependency containers must be in the same namespace, to ensure there are never problems resolving a dependency. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 112 ++++++++++++++++++++++++++++++++---- libpod/boltdb_state_internal.go | 50 +++++++++++++++- libpod/boltdb_state_linux.go | 8 +++ libpod/in_memory_state.go | 19 ++++++- libpod/options.go | 25 +++++++- libpod/runtime.go | 16 +++++- libpod/state.go | 4 ++ libpod/state_test.go | 123 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 340 insertions(+), 17 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 648c14267..73a906761 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1,6 +1,7 @@ package libpod import ( + "bytes" "encoding/json" "os" "strings" @@ -11,10 +12,12 @@ import ( // 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 } // NewBoltState creates a new bolt-backed state database @@ -23,6 +26,8 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { state.dbPath = path state.lockDir = lockDir state.runtime = runtime + state.namespace = "" + state.namespaceBytes = nil // Make the directory that will hold container lockfiles if err := os.MkdirAll(lockDir, 0750); err != nil { @@ -47,6 +52,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 +201,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,6 +284,11 @@ 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)) @@ -274,6 +301,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) @@ -334,7 +369,14 @@ func (s *BoltState) HasContainer(id string) (bool, error) { ctrExists := ctrBucket.Bucket(ctrID) if ctrExists != nil { - exists = true + if s.namespaceBytes != nil { + nsBytes := ctrBucket.Get(namespaceKey) + if bytes.Equal(nsBytes, nsBytes) { + exists = true + } + } else { + exists = true + } } return nil @@ -383,7 +425,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 removeContainer(ctr, nil, tx, s.namespace) }) return err } @@ -398,6 +440,12 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { return ErrCtrRemoved } + 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) + } + } + newState := new(containerState) netNSPath := "" @@ -460,6 +508,12 @@ func (s *BoltState) SaveContainer(ctr *Container) error { return ErrCtrRemoved } + 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) + } + } + stateJSON, err := json.Marshal(ctr.state) if err != nil { return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID()) @@ -519,6 +573,12 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { return nil, ErrCtrRemoved } + if s.namespace != "" { + if 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 +662,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,6 +753,11 @@ 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)) @@ -694,6 +770,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 +803,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 +840,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 @@ -1318,7 +1408,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 removeContainer(ctr, pod, tx, s.namespace) }) return err } diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 5661c5b7f..718c43046 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -16,6 +16,7 @@ import ( const ( idRegistryName = "id-registry" nameRegistryName = "name-registry" + nsRegistryName = "ns-registry" ctrName = "ctr" allCtrsName = "all-ctrs" podName = "pod" @@ -34,6 +35,7 @@ const ( var ( idRegistryBkt = []byte(idRegistryName) nameRegistryBkt = []byte(nameRegistryName) + nsRegistryBkt = []byte(nsRegistryName) ctrBkt = []byte(ctrName) allCtrsBkt = []byte(allCtrsName) podBkt = []byte(podName) @@ -168,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 { @@ -214,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)) @@ -287,6 +304,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 @@ -343,6 +365,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()) } @@ -403,6 +430,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) @@ -427,7 +459,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 removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx, namespace string) error { ctrID := []byte(ctr.ID()) ctrName := []byte(ctr.Name()) @@ -446,6 +478,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 @@ -475,6 +512,14 @@ 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 namespace != "" { + if 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, namespace) + } + } + if podDB != nil { // Check if the container is in the pod, remove it if it is podCtrs := podDB.Bucket(containersBkt) @@ -521,6 +566,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/in_memory_state.go b/libpod/in_memory_state.go index cf2f43477..4ea8740fb 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -20,6 +20,7 @@ type InMemoryState struct { podContainers map[string]map[string]*Container nameIndex *registrar.Registrar idIndex *truncindex.TruncIndex + namespace string } // NewInMemoryState initializes a new, empty in-memory state @@ -36,6 +37,8 @@ func NewInMemoryState() (State, error) { state.nameIndex = registrar.NewRegistrar() state.idIndex = truncindex.NewTruncIndex([]string{}) + state.namespace = "" + return state, nil } @@ -51,6 +54,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 == "" { @@ -133,6 +143,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 { @@ -519,9 +532,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 diff --git a/libpod/options.go b/libpod/options.go index fb07d1edf..155c15333 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -284,6 +284,27 @@ func WithCNIPluginDir(dir string) RuntimeOption { } } +// WithNamespace sets the namespace for libpod. +// 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. +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. @@ -963,11 +984,11 @@ func WithRootFS(rootfs string) CtrCreateOption { } } -// WithNamespace sets the namespace the container will be created in. +// 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 WithNamespace(ns string) CtrCreateOption { +func WithCtrNamespace(ns string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized diff --git a/libpod/runtime.go b/libpod/runtime.go index a551c9134..a0b673d81 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 ( diff --git a/libpod/state.go b/libpod/state.go index b71f811ea..6239e399a 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -9,6 +9,10 @@ type State interface { // Refresh clears container and pod states after a reboot Refresh() error + // SetNamespace() sets the namespace for the store, and will determine + // what containers are retrieved with container and pod retrieval calls + SetNamespace(ns string) error + // Return a container from the database from its full ID Container(id string) (*Container, error) // Return a container from the database by full or partial ID or full diff --git a/libpod/state_test.go b/libpod/state_test.go index 7174bbf2a..e5c5a1de0 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -281,6 +281,56 @@ 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 TestGetNonexistentContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.Container("does not exist") @@ -2224,6 +2274,79 @@ 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) -- cgit v1.2.3-54-g00ecf From 2705344634a875c49a4c9028d3a2f7e334b4db1f Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 3 Jul 2018 11:12:00 -0400 Subject: Untested implementation of namespaced BoltDB access All BoltDB access and update functions now understand namespaces. Accessing containers outside of your namespace will produce errors, except for Lookup and All functions, which will perform their tasks only on containers within your namespace. The "" namespace remains a reserved, no-restrictions namespace. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 94 ++++++++++++++++++++++++++++++++++++++--- libpod/boltdb_state_internal.go | 11 +++-- 2 files changed, 96 insertions(+), 9 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 73a906761..470201348 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -371,7 +371,7 @@ func (s *BoltState) HasContainer(id string) (bool, error) { if ctrExists != nil { if s.namespaceBytes != nil { nsBytes := ctrBucket.Get(namespaceKey) - if bytes.Equal(nsBytes, nsBytes) { + if bytes.Equal(nsBytes, s.namespaceBytes) { exists = true } } else { @@ -425,7 +425,7 @@ func (s *BoltState) RemoveContainer(ctr *Container) error { defer db.Close() err = db.Update(func(tx *bolt.Tx) error { - return removeContainer(ctr, nil, tx, s.namespace) + return s.removeContainer(ctr, nil, tx) }) return err } @@ -873,6 +873,12 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { return false, ErrPodRemoved } + if s.namespace != "" { + if 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()) @@ -903,6 +909,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 @@ -927,6 +938,12 @@ func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { return nil, ErrPodRemoved } + if s.namespace != "" { + if 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{} @@ -985,6 +1002,12 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { return nil, ErrPodRemoved } + if s.namespace != "" { + if 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{} @@ -1051,6 +1074,12 @@ func (s *BoltState) AddPod(pod *Pod) 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) + } + } + podID := []byte(pod.ID()) podName := []byte(pod.Name()) @@ -1096,6 +1125,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 { @@ -1130,6 +1164,9 @@ func (s *BoltState) AddPod(pod *Pod) error { 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 @@ -1163,6 +1200,12 @@ func (s *BoltState) RemovePod(pod *Pod) 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) + } + } + podID := []byte(pod.ID()) podName := []byte(pod.Name()) @@ -1193,6 +1236,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 { @@ -1221,6 +1269,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()) } @@ -1247,6 +1298,12 @@ func (s *BoltState) RemovePodContainers(pod *Pod) 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) + } + } + podID := []byte(pod.ID()) db, err := s.getDBCon() @@ -1393,6 +1450,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()) } @@ -1408,7 +1474,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, s.namespace) + return s.removeContainer(ctr, pod, tx) }) return err } @@ -1423,6 +1489,12 @@ func (s *BoltState) UpdatePod(pod *Pod) 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) + } + } + newState := new(podState) db, err := s.getDBCon() @@ -1476,6 +1548,12 @@ func (s *BoltState) SavePod(pod *Pod) 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) + } + } + stateJSON, err := json.Marshal(pod.state) if err != nil { return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID()) @@ -1552,9 +1630,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 718c43046..81c9f49f5 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -459,7 +459,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, namespace string) error { +func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { ctrID := []byte(ctr.ID()) ctrName := []byte(ctr.Name()) @@ -514,9 +514,12 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx, namespace string) er // Compare namespace // We can't remove containers not in our namespace - if namespace != "" { - if 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, 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) } } -- cgit v1.2.3-54-g00ecf From 92e6bd01a8623a61e37217155508856eb2be3316 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 12 Jul 2018 10:25:49 -0400 Subject: Add namespaces to in memory state Signed-off-by: Matthew Heon --- libpod/in_memory_state.go | 272 +++++++++++++++++++++++++++++++++++++++++++--- libpod/state_test.go | 9 ++ 2 files changed, 264 insertions(+), 17 deletions(-) (limited to 'libpod') diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 4ea8740fb..265170284 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -14,13 +14,19 @@ 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 - podContainers map[string]map[string]*Container - nameIndex *registrar.Registrar - idIndex *truncindex.TruncIndex - namespace string + pods map[string]*Pod + containers map[string]*Container + ctrDepends map[string][]string + podContainers map[string]map[string]*Container + nameIndex *registrar.Registrar + idIndex *truncindex.TruncIndex + namespace string + namespaceIndexes map[string]*namespaceIndex +} + +type namespaceIndex struct { + nameIndex *registrar.Registrar + idIndex *truncindex.TruncIndex } // NewInMemoryState initializes a new, empty in-memory state @@ -39,6 +45,8 @@ func NewInMemoryState() (State, error) { state.namespace = "" + state.namespaceIndexes = make(map[string]*namespaceIndex) + return state, nil } @@ -72,20 +80,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) @@ -112,9 +143,18 @@ func (s *InMemoryState) HasContainer(id string) (bool, error) { return false, ErrEmptyID } - _, ok := s.containers[id] + ctr, ok := s.containers[id] + if ok { + if s.namespace != "" { + if s.namespace != ctr.config.Namespace { + return false, nil + } + return true, nil + } + return true, nil + } - return ok, nil + return false, nil } // AddContainer adds a container to the state @@ -159,6 +199,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) @@ -173,6 +232,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 { @@ -193,6 +256,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 { @@ -217,6 +291,10 @@ func (s *InMemoryState) UpdateContainer(ctr *Container) error { return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) } + if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil { + return err + } + return nil } @@ -236,6 +314,10 @@ func (s *InMemoryState) SaveContainer(ctr *Container) error { return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) } + if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil { + return err + } + return nil } @@ -245,6 +327,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 @@ -257,7 +349,13 @@ 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 != "" { + if ctr.config.Namespace == s.namespace { + ctrs = append(ctrs, ctr) + } + } else { + ctrs = append(ctrs, ctr) + } } return ctrs, nil @@ -274,21 +372,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) @@ -315,9 +436,18 @@ func (s *InMemoryState) HasPod(id string) (bool, error) { return false, ErrEmptyID } - _, ok := s.pods[id] + pod, ok := s.pods[id] + if ok { + if s.namespace != "" { + if s.namespace != pod.config.Namespace { + return false, nil + } + return true, nil + } + return true, nil + } - return ok, nil + return false, nil } // PodHasContainer checks if the given pod has a container with the given ID @@ -330,6 +460,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 @@ -346,6 +480,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 @@ -371,6 +509,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 @@ -417,6 +559,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 } @@ -426,6 +587,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()) @@ -446,6 +611,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 } @@ -458,6 +634,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 { @@ -560,6 +740,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) @@ -578,6 +777,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 { @@ -617,6 +820,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 { @@ -633,6 +847,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()) @@ -648,6 +866,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()) @@ -660,7 +882,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 @@ -705,3 +933,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/state_test.go b/libpod/state_test.go index e5c5a1de0..8a678ba82 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -634,6 +634,15 @@ 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 TestContainerInUseOneContainer(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr1, err := getTestCtr1(lockPath) -- cgit v1.2.3-54-g00ecf From 572fd75d226550ac1576bf38812e5417a9eddeee Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 18 Jul 2018 13:47:08 -0400 Subject: Add tests for state namespacing Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 9 +- libpod/state_test.go | 723 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 720 insertions(+), 12 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 470201348..b2a246ca8 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -8,6 +8,7 @@ import ( "github.com/boltdb/bolt" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // BoltState is a state implementation backed by a Bolt DB @@ -29,6 +30,8 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { 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 { // The directory is allowed to exist @@ -367,10 +370,10 @@ func (s *BoltState) HasContainer(id string) (bool, error) { return err } - ctrExists := ctrBucket.Bucket(ctrID) - if ctrExists != nil { + ctrDB := ctrBucket.Bucket(ctrID) + if ctrDB != nil { if s.namespaceBytes != nil { - nsBytes := ctrBucket.Get(namespaceKey) + nsBytes := ctrDB.Get(namespaceKey) if bytes.Equal(nsBytes, s.namespaceBytes) { exists = true } diff --git a/libpod/state_test.go b/libpod/state_test.go index 8a678ba82..0c924a1f1 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -345,6 +345,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("") @@ -448,6 +501,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("") @@ -491,6 +604,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) @@ -516,6 +665,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) @@ -534,6 +712,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"}}) @@ -552,6 +747,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) @@ -584,6 +796,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() @@ -627,6 +866,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{}) @@ -638,7 +921,24 @@ 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) + _, 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) }) } @@ -1031,6 +1331,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("") @@ -1135,6 +1471,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("") @@ -1155,38 +1554,74 @@ func TestHasPodWrongIDFalse(t *testing.T) { testPod, err := getTestPod1(lockPath) assert.NoError(t, err) - err = state.AddPod(testPod) + err = state.AddPod(testPod) + assert.NoError(t, err) + + exist, err := state.HasPod(strings.Repeat("a", 32)) + assert.NoError(t, err) + assert.False(t, exist) + }) +} + +func TestHasPodRightIDTrue(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + err = state.AddPod(testPod) + assert.NoError(t, err) + + exist, err := state.HasPod(testPod.ID()) + assert.NoError(t, err) + assert.True(t, exist) + }) +} + +func TestHasPodCtrIDFalse(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) assert.NoError(t, err) - exist, err := state.HasPod(strings.Repeat("a", 32)) + exist, err := state.HasPod(testCtr.ID()) assert.NoError(t, err) assert.False(t, exist) }) } -func TestHasPodRightIDTrue(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 TestHasPodCtrIDFalse(t *testing.T) { +func TestHasPodDifferentNamespaceFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { - testCtr, err := getTestCtr1(lockPath) + testPod, err := getTestPod1(lockPath) assert.NoError(t, err) - err = state.AddContainer(testCtr) + testPod.config.Namespace = "test1" + + err = state.AddPod(testPod) assert.NoError(t, err) - exist, err := state.HasPod(testCtr.ID()) + state.SetNamespace("test2") + + exist, err := state.HasPod(testPod.ID()) assert.NoError(t, err) assert.False(t, exist) }) @@ -1427,6 +1862,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() @@ -1489,6 +1947,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)) @@ -1565,6 +2069,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{}}) @@ -1667,6 +2188,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{}}) @@ -1674,7 +2212,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) @@ -1770,6 +2308,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{}}) @@ -1926,6 +2481,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) @@ -2620,6 +3192,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{}}) @@ -2637,6 +3279,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{}}) @@ -2654,6 +3313,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) @@ -2678,3 +3354,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) + }) +} -- cgit v1.2.3-54-g00ecf From 7b30659629deaddafc7fc925d869324ae754c216 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 11:24:42 -0400 Subject: Enforce namespace checks on container add Signed-off-by: Matthew Heon --- libpod/boltdb_state_internal.go | 5 ++ libpod/in_memory_state.go | 8 ++++ libpod/state_test.go | 100 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 3 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 81c9f49f5..b03c11531 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -266,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) diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 265170284..55be89d4c 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -172,6 +172,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. @@ -692,6 +696,10 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { 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 { diff --git a/libpod/state_test.go b/libpod/state_test.go index 0c924a1f1..4e9ba8850 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -331,6 +331,45 @@ func TestAddCtrDepInDifferentNamespaceFails(t *testing.T) { }) } +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") @@ -2493,7 +2532,7 @@ func TestRemoveContainersNotInNamespace(t *testing.T) { state.SetNamespace("test2") - err := state.RemovePodContainers(testPod) + err = state.RemovePodContainers(testPod) assert.Error(t, err) }) } @@ -3019,6 +3058,61 @@ func TestAddContainerToPodNamespaceOnPodFails(t *testing.T) { }) } +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) @@ -3291,7 +3385,7 @@ func TestUpdatePodNotInNamespaceFails(t *testing.T) { state.SetNamespace("test2") - _, err = state.UpdatePod(testPod) + err = state.UpdatePod(testPod) assert.Error(t, err) }) } @@ -3325,7 +3419,7 @@ func TestSavePodNotInNamespaceFails(t *testing.T) { state.SetNamespace("test2") - _, err = state.SavePod(testPod) + err = state.SavePod(testPod) assert.Error(t, err) }) } -- cgit v1.2.3-54-g00ecf From 84afa32493335e74d6094c3723c53cf38ac9e6bb Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 12:04:44 -0400 Subject: Ensure pods are part of the set namespace when added Signed-off-by: Matthew Heon --- libpod/in_memory_state.go | 4 ++++ libpod/state_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'libpod') diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 55be89d4c..e323b069c 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -542,6 +542,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()) } diff --git a/libpod/state_test.go b/libpod/state_test.go index 4e9ba8850..30638024c 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -1790,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{}}) -- cgit v1.2.3-54-g00ecf From 8f91678a499d29cb5221153e223f5e896cb1b5b1 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 12:05:14 -0400 Subject: Update documentation for the State interface Include details on how namespaces interact with the state. Signed-off-by: Matthew Heon --- libpod/state.go | 105 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 33 deletions(-) (limited to 'libpod') diff --git a/libpod/state.go b/libpod/state.go index 6239e399a..1b82349b3 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -10,70 +10,109 @@ type State interface { Refresh() error // SetNamespace() sets the namespace for the store, and will determine - // what containers are retrieved with container and pod retrieval calls + // 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 + // 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) } -- cgit v1.2.3-54-g00ecf From 3ae0c80806b68f712756fd660d06449e71eb41b7 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 14:24:22 -0400 Subject: Add --namespace flag to Podman Allows joining libpod to a specific namespace when running a Podman command. Signed-off-by: Matthew Heon --- cmd/podman/libpodruntime/runtime.go | 4 ++++ cmd/podman/main.go | 5 +++++ completions/bash/podman | 1 + docs/podman.1.md | 5 +++++ libpod/options.go | 1 - 5 files changed, 15 insertions(+), 1 deletion(-) (limited to 'libpod') 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..9ae45e056 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -172,6 +172,11 @@ func main() { Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", Value: "error", }, + cli.StringFlag{ + Name: "namespace", + Usage: "set the libpod namespace, used 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..ffc2669a4 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -39,6 +39,11 @@ Path to where the cpu performance results should be written log messages above specified level: debug, info, warn, error (default), fatal or panic +**--namespace** + +set namespace 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** Path to the root directory in which data, including images, is stored diff --git a/libpod/options.go b/libpod/options.go index 155c15333..7bb4a3632 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -285,7 +285,6 @@ func WithCNIPluginDir(dir string) RuntimeOption { } // WithNamespace sets the namespace for libpod. -// 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 -- cgit v1.2.3-54-g00ecf From fc95f68247248e4cb029440e2fe4ba0b0df8049c Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 14:29:00 -0400 Subject: Set namespace for new pods/containers based on runtime New containers and pods will default to the namespace of the runtime, but this can be overridden by With... options if desired. Signed-off-by: Matthew Heon --- libpod/runtime_ctr.go | 6 ++++++ libpod/runtime_pod_linux.go | 6 ++++++ 2 files changed, 12 insertions(+) (limited to 'libpod') 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") -- cgit v1.2.3-54-g00ecf From 7a358e427738294180a14b1298dfc3a569f0e0fc Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 17:21:27 -0400 Subject: Address first round of review comments Signed-off-by: Matthew Heon --- cmd/podman/main.go | 2 +- docs/podman.1.md | 2 +- libpod/boltdb_state.go | 66 ++++++++++++++++------------------------------- libpod/in_memory_state.go | 36 ++++++-------------------- 4 files changed, 32 insertions(+), 74 deletions(-) (limited to 'libpod') diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 9ae45e056..dbd7c1155 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -174,7 +174,7 @@ func main() { }, cli.StringFlag{ Name: "namespace", - Usage: "set the libpod namespace, used create separate views of the containers and pods on the system", + Usage: "set the libpod namespace, used to create separate views of the containers and pods on the system", Value: "", }, cli.StringFlag{ diff --git a/docs/podman.1.md b/docs/podman.1.md index ffc2669a4..41c427ec6 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -41,7 +41,7 @@ log messages above specified level: debug, info, warn, error (default), fatal or **--namespace** -set namespace libpod namespace. Namespaces are used to separate groups of containers and pods in libpod's state. +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/boltdb_state.go b/libpod/boltdb_state.go index b2a246ca8..24785248f 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -443,10 +443,8 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { return ErrCtrRemoved } - 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 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) @@ -511,10 +509,8 @@ func (s *BoltState) SaveContainer(ctr *Container) error { return ErrCtrRemoved } - 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 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) @@ -576,10 +572,8 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { return nil, ErrCtrRemoved } - if s.namespace != "" { - if 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) - } + 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{} @@ -876,10 +870,8 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { return false, ErrPodRemoved } - if s.namespace != "" { - if 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) - } + 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) @@ -941,10 +933,8 @@ func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { return nil, ErrPodRemoved } - if s.namespace != "" { - if 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) - } + 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()) @@ -1005,10 +995,8 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { return nil, ErrPodRemoved } - if s.namespace != "" { - if 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) - } + 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()) @@ -1077,10 +1065,8 @@ func (s *BoltState) AddPod(pod *Pod) 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 != "" && 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()) @@ -1203,10 +1189,8 @@ func (s *BoltState) RemovePod(pod *Pod) 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 != "" && 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()) @@ -1301,10 +1285,8 @@ func (s *BoltState) RemovePodContainers(pod *Pod) 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 != "" && 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()) @@ -1492,10 +1474,8 @@ func (s *BoltState) UpdatePod(pod *Pod) 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 != "" && 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) @@ -1551,10 +1531,8 @@ func (s *BoltState) SavePod(pod *Pod) 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 != "" && 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) diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index e323b069c..d421a5e8b 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -144,17 +144,11 @@ func (s *InMemoryState) HasContainer(id string) (bool, error) { } ctr, ok := s.containers[id] - if ok { - if s.namespace != "" { - if s.namespace != ctr.config.Namespace { - return false, nil - } - return true, nil - } - return true, nil + if !ok || (s.namespace != "" && s.namespace != ctr.config.Namespace) { + return false, nil } - return false, nil + return true, nil } // AddContainer adds a container to the state @@ -295,11 +289,7 @@ func (s *InMemoryState) UpdateContainer(ctr *Container) error { return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) } - if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil { - return err - } - - return nil + return s.checkNSMatch(ctr.ID(), ctr.Namespace()) } // SaveContainer saves a container's state @@ -318,11 +308,7 @@ func (s *InMemoryState) SaveContainer(ctr *Container) error { return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) } - if err := s.checkNSMatch(ctr.ID(), ctr.Namespace()); err != nil { - return err - } - - return nil + return s.checkNSMatch(ctr.ID(), ctr.Namespace()) } // ContainerInUse checks if the given container is being used by other containers @@ -441,17 +427,11 @@ func (s *InMemoryState) HasPod(id string) (bool, error) { } pod, ok := s.pods[id] - if ok { - if s.namespace != "" { - if s.namespace != pod.config.Namespace { - return false, nil - } - return true, nil - } - return true, nil + if !ok || (s.namespace != "" && s.namespace != pod.config.Namespace) { + return false, nil } - return false, nil + return true, nil } // PodHasContainer checks if the given pod has a container with the given ID -- cgit v1.2.3-54-g00ecf From 486c5c87bca028ba41dfb9f516ae37b9d6a984cb Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 20 Jul 2018 08:49:30 -0400 Subject: Add missing runtime.go lines to set namespace Also add namespace to inspect output to verify its presence Signed-off-by: Matthew Heon --- libpod/container_inspect.go | 1 + libpod/runtime.go | 5 +++++ pkg/inspect/inspect.go | 1 + 3 files changed, 7 insertions(+) (limited to 'libpod') 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/runtime.go b/libpod/runtime.go index a0b673d81..1a384fde2 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -505,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/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. -- cgit v1.2.3-54-g00ecf From 1b51e88098e0c77cddd8de3484ef56965352bcf3 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 24 Jul 2018 16:11:26 -0400 Subject: Update comments in BoltDB and In-Memory states Better explain the inner workings of both state types in comments to make reviews and changes easier. Signed-off-by: Matthew Heon --- docs/podman.1.md | 4 ++-- libpod/boltdb_state.go | 36 ++++++++++++++++++++++++++++++++++-- libpod/in_memory_state.go | 31 +++++++++++++++++++------------ 3 files changed, 55 insertions(+), 16 deletions(-) (limited to 'libpod') diff --git a/docs/podman.1.md b/docs/podman.1.md index 41c427ec6..1ca420d12 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -37,11 +37,11 @@ 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. +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/boltdb_state.go b/libpod/boltdb_state.go index 24785248f..afbaecffb 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -21,6 +21,34 @@ type BoltState struct { 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) @@ -296,7 +324,9 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { 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. @@ -759,7 +789,9 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { 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. diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index d421a5e8b..8bdd0881c 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -14,16 +14,27 @@ 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 - podContainers map[string]map[string]*Container - nameIndex *registrar.Registrar - idIndex *truncindex.TruncIndex - namespace 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 + // 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 @@ -339,11 +350,7 @@ func (s *InMemoryState) ContainerInUse(ctr *Container) ([]string, error) { func (s *InMemoryState) AllContainers() ([]*Container, error) { ctrs := make([]*Container, 0, len(s.containers)) for _, ctr := range s.containers { - if s.namespace != "" { - if ctr.config.Namespace == s.namespace { - ctrs = append(ctrs, ctr) - } - } else { + if s.namespace == "" || ctr.config.Namespace == s.namespace { ctrs = append(ctrs, ctr) } } -- cgit v1.2.3-54-g00ecf