Add pod state

Add a mutable state to pods, and database backend sutable for
modifying and updating said state.

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Closes: #784
Approved by: rhatdan
This commit is contained in:
Matthew Heon
2018-05-14 19:30:11 -04:00
committed by Atomic Bot
parent c45d4c6d5c
commit 018d2c6b1d
8 changed files with 299 additions and 28 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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))

View File

@ -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

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)
})
}