From 018d2c6b1d23acf7fe67e809498bc354eaf6becf Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 14 May 2018 19:30:11 -0400 Subject: Add pod state Add a mutable state to pods, and database backend sutable for modifying and updating said state. Signed-off-by: Matthew Heon Closes: #784 Approved by: rhatdan --- libpod/boltdb_state.go | 120 +++++++++++++++++++++++++++++++++++++++- libpod/boltdb_state_internal.go | 17 ++++-- libpod/common_test.go | 30 +++++++++- libpod/in_memory_state.go | 30 ++++++++++ libpod/pod.go | 23 +++++++- libpod/runtime_pod.go | 21 +++++++ libpod/state.go | 4 ++ libpod/state_test.go | 82 ++++++++++++++++++++++----- 8 files changed, 299 insertions(+), 28 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index e1f29b16a..77b2fe5b7 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -623,6 +623,7 @@ func (s *BoltState) Pod(id string) (*Pod, error) { pod := new(Pod) pod.config = new(PodConfig) + pod.state = new(podState) db, err := s.getDBCon() if err != nil { @@ -657,6 +658,7 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { pod := new(Pod) pod.config = new(PodConfig) + pod.state = new(podState) db, err := s.getDBCon() if err != nil { @@ -957,9 +959,14 @@ func (s *BoltState) AddPod(pod *Pod) error { podID := []byte(pod.ID()) podName := []byte(pod.Name()) - podJSON, err := json.Marshal(pod.config) + podConfigJSON, err := json.Marshal(pod.config) if err != nil { - return errors.Wrapf(err, "error marshalling pod %s JSON", pod.ID()) + return errors.Wrapf(err, "error marshalling pod %s config to JSON", pod.ID()) + } + + podStateJSON, err := json.Marshal(pod.state) + if err != nil { + return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID()) } db, err := s.getDBCon() @@ -1011,10 +1018,14 @@ func (s *BoltState) AddPod(pod *Pod) error { return errors.Wrapf(err, "error creating bucket for pod %s containers", pod.ID()) } - if err := newPod.Put(configKey, podJSON); err != nil { + if err := newPod.Put(configKey, podConfigJSON); err != nil { return errors.Wrapf(err, "error storing pod %s configuration in DB", pod.ID()) } + if err := newPod.Put(stateKey, podStateJSON); err != nil { + return errors.Wrapf(err, "error storing pod %s state JSON 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()) @@ -1296,6 +1307,108 @@ func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { return err } +// UpdatePod updates a pod's state from the database +func (s *BoltState) UpdatePod(pod *Pod) error { + if !s.valid { + return ErrDBClosed + } + + if !pod.valid { + return ErrPodRemoved + } + + newState := new(podState) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + podID := []byte(pod.ID()) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return errors.Wrapf(ErrNoSuchPod, "no pod with ID %s found in database", pod.ID()) + } + + // Get the pod state JSON + podStateBytes := podDB.Get(stateKey) + if podStateBytes == nil { + return errors.Wrapf(ErrInternal, "pod %s is missing state key in DB", pod.ID()) + } + + if err := json.Unmarshal(podStateBytes, newState); err != nil { + return errors.Wrapf(err, "error unmarshalling pod %s state JSON", pod.ID()) + } + + return nil + }) + if err != nil { + return err + } + + pod.state = newState + + return nil +} + +// SavePod saves a pod's state to the database +func (s *BoltState) SavePod(pod *Pod) error { + if !s.valid { + return ErrDBClosed + } + + if !pod.valid { + return ErrPodRemoved + } + + stateJSON, err := json.Marshal(pod.state) + if err != nil { + return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID()) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + podID := []byte(pod.ID()) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return errors.Wrapf(ErrNoSuchPod, "no pod with ID %s found in database", pod.ID()) + } + + // Set the pod state JSON + if err := podDB.Put(stateKey, stateJSON); err != nil { + return errors.Wrapf(err, "error updating pod %s state in database", pod.ID()) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + // AllPods returns all pods present in the state func (s *BoltState) AllPods() ([]*Pod, error) { if !s.valid { @@ -1331,6 +1444,7 @@ func (s *BoltState) AllPods() ([]*Pod, error) { pod := new(Pod) pod.config = new(PodConfig) + pod.state = new(podState) pods = append(pods, pod) diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 25dd216a3..29ed42f1e 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -256,13 +256,22 @@ func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error return errors.Wrapf(ErrNoSuchPod, "pod with ID %s not found", string(id)) } - podBytes := podDB.Get(configKey) - if podBytes == nil { + podConfigBytes := podDB.Get(configKey) + if podConfigBytes == nil { return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id)) } - if err := json.Unmarshal(podBytes, pod.config); err != nil { - return errors.Wrapf(err, "error unmarshalling pod %s from DB", string(id)) + if err := json.Unmarshal(podConfigBytes, pod.config); err != nil { + return errors.Wrapf(err, "error unmarshalling pod %s config from DB", string(id)) + } + + podStateBytes := podDB.Get(stateKey) + if podStateBytes == nil { + return errors.Wrapf(ErrInternal, "pod %s is missing state key in DB", string(id)) + } + + if err := json.Unmarshal(podStateBytes, pod.state); err != nil { + return errors.Wrapf(err, "error unmarshalling pod %s state from DB", string(id)) } // Get the lock diff --git a/libpod/common_test.go b/libpod/common_test.go index 01dca7acb..a95d73926 100644 --- a/libpod/common_test.go +++ b/libpod/common_test.go @@ -94,9 +94,13 @@ func getTestContainer(id, name, locksDir string) (*Container, error) { func getTestPod(id, name, locksDir string) (*Pod, error) { pod := &Pod{ config: &PodConfig{ - ID: id, - Name: name, - Labels: map[string]string{"a": "b", "c": "d"}, + ID: id, + Name: name, + Labels: map[string]string{"a": "b", "c": "d"}, + CgroupParent: "/hello/world/cgroup/parent", + }, + state: &podState{ + CgroupPath: "/path/to/cgroups/hello/", }, valid: true, } @@ -180,3 +184,23 @@ func testContainersEqual(t *testing.T, a, b *Container) { assert.EqualValues(t, aState, bState) } + +// Test if pods are equal +func testPodsEqual(t *testing.T, a, b *Pod) { + if a == nil && b == nil { + return + } + + assert.NotNil(t, a) + assert.NotNil(t, b) + + assert.NotNil(t, a.config) + assert.NotNil(t, b.config) + assert.NotNil(t, a.state) + assert.NotNil(t, b.state) + + assert.Equal(t, a.valid, b.valid) + + assert.EqualValues(t, a.config, b.config) + assert.EqualValues(t, a.state, b.state) +} diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 386ace5b6..36077b9d1 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -604,6 +604,36 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { return nil } +// UpdatePod updates a pod in the state +// This is a no-op as there is no backing store +func (s *InMemoryState) UpdatePod(pod *Pod) error { + if !pod.valid { + return ErrPodRemoved + } + + if _, ok := s.pods[pod.ID()]; !ok { + pod.valid = false + return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) + } + + return nil +} + +// SavePod updates a pod in the state +// This is a no-op at there is no backing store +func (s *InMemoryState) SavePod(pod *Pod) error { + if !pod.valid { + return ErrPodRemoved + } + + if _, ok := s.pods[pod.ID()]; !ok { + pod.valid = false + return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) + } + + return nil +} + // AllPods retrieves all pods currently in the state func (s *InMemoryState) AllPods() ([]*Pod, error) { pods := make([]*Pod, 0, len(s.pods)) diff --git a/libpod/pod.go b/libpod/pod.go index 3cad3f817..e082ef807 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -15,6 +15,7 @@ import ( // ffjson: skip type Pod struct { config *PodConfig + state *podState valid bool runtime *Runtime @@ -23,9 +24,19 @@ type Pod struct { // PodConfig represents a pod's static configuration type PodConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Labels map[string]string `json:""` + ID string `json:"id"` + Name string `json:"name"` + + // Labels contains labels applied to the pod + Labels map[string]string `json:"labels"` + // CgroupParent contains the pod's CGroup parent + CgroupParent string `json:"cgroupParent"` +} + +// podState represents a pod's state +type podState struct { + // CgroupPath is the path to the pod's CGroup + CgroupPath string } // ID retrieves the pod's ID @@ -48,12 +59,18 @@ func (p *Pod) Labels() map[string]string { return labels } +// CgroupParent returns the pod's CGroup parent +func (p *Pod) CgroupParent() string { + return p.config.CgroupParent +} + // Creates a new, empty pod func newPod(lockDir string, runtime *Runtime) (*Pod, error) { pod := new(Pod) pod.config = new(PodConfig) pod.config.ID = stringid.GenerateNonCryptoID() pod.config.Labels = make(map[string]string) + pod.state = new(podState) pod.runtime = runtime // Path our lock file will reside at diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index 257b980a2..4ca8da9ee 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -1,6 +1,9 @@ package libpod import ( + "path" + "strings" + "github.com/pkg/errors" ) @@ -45,6 +48,24 @@ func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { pod.valid = true + // Check CGroup parent sanity, and set it if it was not set + switch r.config.CgroupManager { + case CgroupfsCgroupsManager: + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = CgroupfsDefaultCgroupParent + } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") + } + case SystemdCgroupsManager: + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = SystemdDefaultCgroupParent + } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") + } + default: + return nil, errors.Wrapf(ErrInvalidArg, "unsupported CGroup manager: %s - cannot validate cgroup parent", r.config.CgroupManager) + } + if err := r.state.AddPod(pod); err != nil { return nil, errors.Wrapf(err, "error adding pod to state") } diff --git a/libpod/state.go b/libpod/state.go index ed52e3d46..9333c1724 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -66,6 +66,10 @@ type State interface { // RemoveContainerFromPod removes a container from an existing pod // The container will also be removed from the state RemoveContainerFromPod(pod *Pod, ctr *Container) error + // UpdatePod updates a pod's state from the database + UpdatePod(pod *Pod) error + // SavePod saves a pod's state to the database + SavePod(pod *Pod) error // Retrieves all pods presently in state AllPods() ([]*Pod, error) } diff --git a/libpod/state_test.go b/libpod/state_test.go index f0c65b134..4d5eb9713 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -915,8 +915,7 @@ func TestGetPodOnePod(t *testing.T) { statePod, err := state.Pod(testPod.ID()) assert.NoError(t, err) - assert.EqualValues(t, testPod.config, statePod.config) - assert.Equal(t, testPod.valid, statePod.valid) + testPodsEqual(t, testPod, statePod) }) } @@ -937,8 +936,7 @@ func TestGetOnePodFromTwo(t *testing.T) { statePod, err := state.Pod(testPod1.ID()) assert.NoError(t, err) - assert.EqualValues(t, testPod1.config, statePod.config) - assert.Equal(t, testPod1.valid, statePod.valid) + testPodsEqual(t, testPod1, statePod) }) } @@ -999,8 +997,7 @@ func TestLookupPodFullID(t *testing.T) { statePod, err := state.LookupPod(testPod.ID()) assert.NoError(t, err) - assert.EqualValues(t, testPod.config, statePod.config) - assert.Equal(t, testPod.valid, statePod.valid) + testPodsEqual(t, testPod, statePod) }) } @@ -1015,8 +1012,7 @@ func TestLookupPodUniquePartialID(t *testing.T) { statePod, err := state.LookupPod(testPod.ID()[0:8]) assert.NoError(t, err) - assert.EqualValues(t, testPod.config, statePod.config) - assert.Equal(t, testPod.valid, statePod.valid) + testPodsEqual(t, testPod, statePod) }) } @@ -1050,8 +1046,7 @@ func TestLookupPodByName(t *testing.T) { statePod, err := state.LookupPod(testPod.Name()) assert.NoError(t, err) - assert.EqualValues(t, testPod.config, statePod.config) - assert.Equal(t, testPod.valid, statePod.valid) + testPodsEqual(t, testPod, statePod) }) } @@ -1157,7 +1152,7 @@ func TestAddPodValidPodSucceeds(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, len(allPods)) - assert.EqualValues(t, testPod.config, allPods[0].config) + testPodsEqual(t, testPod, allPods[0]) assert.Equal(t, testPod.valid, allPods[0].valid) }) } @@ -1318,8 +1313,7 @@ func TestRemovePodFromPods(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, len(allPods)) - assert.EqualValues(t, testPod2.config, allPods[0].config) - assert.Equal(t, testPod2.valid, allPods[0].valid) + testPodsEqual(t, testPod2, allPods[0]) }) } @@ -1394,8 +1388,7 @@ func TestAllPodsFindsPod(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, len(allPods)) - assert.EqualValues(t, testPod.config, allPods[0].config) - assert.Equal(t, testPod.valid, allPods[0].valid) + testPodsEqual(t, testPod, allPods[0]) }) } @@ -2403,3 +2396,62 @@ func TestRemoveContainerFromPodWithDependencySucceedsAfterDepRemoved(t *testing. assert.Equal(t, 0, len(allCtrs)) }) } + +func TestUpdatePodInvalidPod(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + err := state.UpdatePod(&Pod{config: &PodConfig{}}) + assert.Error(t, err) + }) +} + +func TestUpdatePodPodNotInStateFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + 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{}}) + assert.Error(t, err) + }) +} + +func TestSavePodPodNotInStateFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + + 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) + assert.NoError(t, err) + + err = state.AddPod(testPod) + assert.NoError(t, err) + + 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