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 := new(Pod)
pod.config = new(PodConfig) pod.config = new(PodConfig)
pod.state = new(podState)
db, err := s.getDBCon() db, err := s.getDBCon()
if err != nil { if err != nil {
@ -657,6 +658,7 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
pod := new(Pod) pod := new(Pod)
pod.config = new(PodConfig) pod.config = new(PodConfig)
pod.state = new(podState)
db, err := s.getDBCon() db, err := s.getDBCon()
if err != nil { if err != nil {
@ -957,9 +959,14 @@ func (s *BoltState) AddPod(pod *Pod) error {
podID := []byte(pod.ID()) podID := []byte(pod.ID())
podName := []byte(pod.Name()) podName := []byte(pod.Name())
podJSON, err := json.Marshal(pod.config) podConfigJSON, err := json.Marshal(pod.config)
if err != nil { 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() 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()) 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()) 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 // Add us to the ID and names buckets
if err := idsBkt.Put(podID, podName); err != nil { if err := idsBkt.Put(podID, podName); err != nil {
return errors.Wrapf(err, "error storing pod %s ID in DB", pod.ID()) 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 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 // AllPods returns all pods present in the state
func (s *BoltState) AllPods() ([]*Pod, error) { func (s *BoltState) AllPods() ([]*Pod, error) {
if !s.valid { if !s.valid {
@ -1331,6 +1444,7 @@ func (s *BoltState) AllPods() ([]*Pod, error) {
pod := new(Pod) pod := new(Pod)
pod.config = new(PodConfig) pod.config = new(PodConfig)
pod.state = new(podState)
pods = append(pods, pod) 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)) return errors.Wrapf(ErrNoSuchPod, "pod with ID %s not found", string(id))
} }
podBytes := podDB.Get(configKey) podConfigBytes := podDB.Get(configKey)
if podBytes == nil { if podConfigBytes == nil {
return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id)) return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id))
} }
if err := json.Unmarshal(podBytes, pod.config); err != nil { if err := json.Unmarshal(podConfigBytes, pod.config); err != nil {
return errors.Wrapf(err, "error unmarshalling pod %s from DB", string(id)) 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 // 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) { func getTestPod(id, name, locksDir string) (*Pod, error) {
pod := &Pod{ pod := &Pod{
config: &PodConfig{ config: &PodConfig{
ID: id, ID: id,
Name: name, Name: name,
Labels: map[string]string{"a": "b", "c": "d"}, Labels: map[string]string{"a": "b", "c": "d"},
CgroupParent: "/hello/world/cgroup/parent",
},
state: &podState{
CgroupPath: "/path/to/cgroups/hello/",
}, },
valid: true, valid: true,
} }
@ -180,3 +184,23 @@ func testContainersEqual(t *testing.T, a, b *Container) {
assert.EqualValues(t, aState, bState) 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 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 // AllPods retrieves all pods currently in the state
func (s *InMemoryState) AllPods() ([]*Pod, error) { func (s *InMemoryState) AllPods() ([]*Pod, error) {
pods := make([]*Pod, 0, len(s.pods)) pods := make([]*Pod, 0, len(s.pods))

View File

@ -15,6 +15,7 @@ import (
// ffjson: skip // ffjson: skip
type Pod struct { type Pod struct {
config *PodConfig config *PodConfig
state *podState
valid bool valid bool
runtime *Runtime runtime *Runtime
@ -23,9 +24,19 @@ type Pod struct {
// PodConfig represents a pod's static configuration // PodConfig represents a pod's static configuration
type PodConfig struct { type PodConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Labels map[string]string `json:""`
// 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 // ID retrieves the pod's ID
@ -48,12 +59,18 @@ func (p *Pod) Labels() map[string]string {
return labels return labels
} }
// CgroupParent returns the pod's CGroup parent
func (p *Pod) CgroupParent() string {
return p.config.CgroupParent
}
// Creates a new, empty pod // Creates a new, empty pod
func newPod(lockDir string, runtime *Runtime) (*Pod, error) { func newPod(lockDir string, runtime *Runtime) (*Pod, error) {
pod := new(Pod) pod := new(Pod)
pod.config = new(PodConfig) pod.config = new(PodConfig)
pod.config.ID = stringid.GenerateNonCryptoID() pod.config.ID = stringid.GenerateNonCryptoID()
pod.config.Labels = make(map[string]string) pod.config.Labels = make(map[string]string)
pod.state = new(podState)
pod.runtime = runtime pod.runtime = runtime
// Path our lock file will reside at // Path our lock file will reside at

View File

@ -1,6 +1,9 @@
package libpod package libpod
import ( import (
"path"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -45,6 +48,24 @@ func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) {
pod.valid = true 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 { if err := r.state.AddPod(pod); err != nil {
return nil, errors.Wrapf(err, "error adding pod to state") 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 // RemoveContainerFromPod removes a container from an existing pod
// The container will also be removed from the state // The container will also be removed from the state
RemoveContainerFromPod(pod *Pod, ctr *Container) error 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 // Retrieves all pods presently in state
AllPods() ([]*Pod, error) AllPods() ([]*Pod, error)
} }

View File

@ -915,8 +915,7 @@ func TestGetPodOnePod(t *testing.T) {
statePod, err := state.Pod(testPod.ID()) statePod, err := state.Pod(testPod.ID())
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testPod.config, statePod.config) testPodsEqual(t, testPod, statePod)
assert.Equal(t, testPod.valid, statePod.valid)
}) })
} }
@ -937,8 +936,7 @@ func TestGetOnePodFromTwo(t *testing.T) {
statePod, err := state.Pod(testPod1.ID()) statePod, err := state.Pod(testPod1.ID())
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testPod1.config, statePod.config) testPodsEqual(t, testPod1, statePod)
assert.Equal(t, testPod1.valid, statePod.valid)
}) })
} }
@ -999,8 +997,7 @@ func TestLookupPodFullID(t *testing.T) {
statePod, err := state.LookupPod(testPod.ID()) statePod, err := state.LookupPod(testPod.ID())
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testPod.config, statePod.config) testPodsEqual(t, testPod, statePod)
assert.Equal(t, testPod.valid, statePod.valid)
}) })
} }
@ -1015,8 +1012,7 @@ func TestLookupPodUniquePartialID(t *testing.T) {
statePod, err := state.LookupPod(testPod.ID()[0:8]) statePod, err := state.LookupPod(testPod.ID()[0:8])
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testPod.config, statePod.config) testPodsEqual(t, testPod, statePod)
assert.Equal(t, testPod.valid, statePod.valid)
}) })
} }
@ -1050,8 +1046,7 @@ func TestLookupPodByName(t *testing.T) {
statePod, err := state.LookupPod(testPod.Name()) statePod, err := state.LookupPod(testPod.Name())
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, testPod.config, statePod.config) testPodsEqual(t, testPod, statePod)
assert.Equal(t, testPod.valid, statePod.valid)
}) })
} }
@ -1157,7 +1152,7 @@ func TestAddPodValidPodSucceeds(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(allPods)) 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) assert.Equal(t, testPod.valid, allPods[0].valid)
}) })
} }
@ -1318,8 +1313,7 @@ func TestRemovePodFromPods(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(allPods)) assert.Equal(t, 1, len(allPods))
assert.EqualValues(t, testPod2.config, allPods[0].config) testPodsEqual(t, testPod2, allPods[0])
assert.Equal(t, testPod2.valid, allPods[0].valid)
}) })
} }
@ -1394,8 +1388,7 @@ func TestAllPodsFindsPod(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(allPods)) 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)
}) })
} }
@ -2403,3 +2396,62 @@ func TestRemoveContainerFromPodWithDependencySucceedsAfterDepRemoved(t *testing.
assert.Equal(t, 0, len(allCtrs)) 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)
})
}