mirror of
https://github.com/containers/podman.git
synced 2025-07-15 03:02:52 +08:00
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:
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user