diff options
author | Matthew Heon <matthew.heon@gmail.com> | 2017-12-13 22:02:15 -0500 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-02-12 14:28:07 +0000 |
commit | b4cdc27b31a638322fb83b64aea869ce8600ea01 (patch) | |
tree | 5d248aeafa1f54a2c169d1d51deaaeb5de1e0e23 /libpod | |
parent | 2e96acf3007cbd05a37a8af7156f0abda073cb5a (diff) | |
download | podman-b4cdc27b31a638322fb83b64aea869ce8600ea01.tar.gz podman-b4cdc27b31a638322fb83b64aea869ce8600ea01.tar.bz2 podman-b4cdc27b31a638322fb83b64aea869ce8600ea01.zip |
Add implementation for BoltDB-backed state
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Closes: #184
Approved by: baude
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 932 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 204 | ||||
-rw-r--r-- | libpod/container.go | 6 | ||||
-rw-r--r-- | libpod/in_memory_state.go | 4 | ||||
-rw-r--r-- | libpod/runtime.go | 13 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 2 | ||||
-rw-r--r-- | libpod/sql_state.go | 3 | ||||
-rw-r--r-- | libpod/state_test.go | 28 |
8 files changed, 1182 insertions, 10 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go new file mode 100644 index 000000000..5502ec1b4 --- /dev/null +++ b/libpod/boltdb_state.go @@ -0,0 +1,932 @@ +package libpod + +import ( + "encoding/json" + "os" + "strings" + + "github.com/boltdb/bolt" + "github.com/pkg/errors" +) + +// BoltState is a state implementation backed by a Bolt DB +type BoltState struct { + valid bool + dbPath string + lockDir string + runtime *Runtime +} + +// NewBoltState creates a new bolt-backed state database +func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { + state := new(BoltState) + state.dbPath = path + state.lockDir = lockDir + state.runtime = runtime + + // Make the directory that will hold container lockfiles + if err := os.MkdirAll(lockDir, 0750); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return nil, errors.Wrapf(err, "error creating lockfiles dir %s", lockDir) + } + } + state.lockDir = lockDir + + db, err := bolt.Open(path, 0600, nil) + if err != nil { + return nil, errors.Wrapf(err, "error opening database %s", path) + } + defer db.Close() + + // Perform initial database setup + err = db.Update(func(tx *bolt.Tx) error { + _, err = tx.CreateBucketIfNotExists(idRegistryBkt) + if err != nil { + return errors.Wrapf(err, "error creating id-registry bucket") + } + _, err = tx.CreateBucketIfNotExists(nameRegistryBkt) + if err != nil { + return errors.Wrapf(err, "error creating name-registry bucket") + } + _, err = tx.CreateBucketIfNotExists(ctrConfigBkt) + if err != nil { + return errors.Wrapf(err, "error creating container-config bucket") + } + _, err = tx.CreateBucketIfNotExists(ctrStateBkt) + if err != nil { + return errors.Wrapf(err, "error creating container-state bucket") + } + _, err = tx.CreateBucketIfNotExists(netNSBkt) + if err != nil { + return errors.Wrapf(err, "error creating net-ns bucket") + } + _, err = tx.CreateBucketIfNotExists(runtimeConfigBkt) + if err != nil { + return errors.Wrapf(err, "error creating runtime-config bucket") + } + _, err = tx.CreateBucketIfNotExists(ctrDependsBkt) + if err != nil { + return errors.Wrapf(err, "error creating container-depends bucket") + } + return nil + }) + if err != nil { + return nil, errors.Wrapf(err, "error creating initial database layout") + } + + // Check runtime configuration + if err := checkRuntimeConfig(db, runtime); err != nil { + return nil, err + } + + state.valid = true + + return state, nil +} + +// Close closes the state and prevents further use +func (s *BoltState) Close() error { + s.valid = false + return nil +} + +// Refresh clears container and pod states after a reboot +func (s *BoltState) Refresh() error { + if !s.valid { + return ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + err = db.Update(func(tx *bolt.Tx) error { + idBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + // Iterate through all IDs. Check if they are containers. + // If they are, unmarshal their state, and then clear + // PID, mountpoint, and state for all of them + // Then save the modified state + // Also clear all network namespaces + err = idBucket.ForEach(func(id, name []byte) error { + if err := netNSBucket.Delete(id); err != nil { + return errors.Wrapf(err, "error removing network namespace ID %s", string(id)) + } + + stateBytes := ctrStateBucket.Get(id) + if stateBytes == nil { + // This is a pod, not a container + // Nothing to do + return nil + } + + state := new(containerState) + + if err := json.Unmarshal(stateBytes, state); err != nil { + return errors.Wrapf(err, "error unmarshalling state for container %s", string(id)) + } + + state.PID = 0 + state.Mountpoint = "" + state.Mounted = false + state.State = ContainerStateConfigured + + newStateBytes, err := json.Marshal(state) + if err != nil { + return errors.Wrapf(err, "error marshalling modified state for container %s", string(id)) + } + + if err := ctrStateBucket.Put(id, newStateBytes); err != nil { + return errors.Wrapf(err, "error updating state for container %s in DB", string(id)) + } + + return nil + }) + return err + }) + return err +} + +// Container retrieves a single container from the state by its full ID +func (s *BoltState) Container(id string) (*Container, error) { + if id == "" { + return nil, ErrEmptyID + } + + if !s.valid { + return nil, ErrDBClosed + } + + ctrID := []byte(id) + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(containerState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + ctrConfigBucket, err := getCtrConfigBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + err = s.getContainerFromDB(ctrID, ctr, ctrConfigBucket, + ctrStateBucket, netNSBucket) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return ctr, nil +} + +// LookupContainer retrieves a container from the state by full or unique +// partial ID or name +func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { + if idOrName == "" { + return nil, ErrEmptyID + } + + if !s.valid { + return nil, ErrDBClosed + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(containerState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + idBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + ctrConfigBucket, err := getCtrConfigBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + // First, check if the ID given was the actual container ID + // Query against state because it's a lot smaller than config + var id []byte + stateExists := ctrStateBucket.Get([]byte(idOrName)) + if stateExists != nil { + // A full container ID was given + id = []byte(idOrName) + } else { + // They did not give us a full container ID. + // Search for partial ID or full name matches + // Use else-if in case the name is set to a partial ID + exists := false + err = idBucket.ForEach(func(checkID, checkName []byte) error { + if string(checkName) == idOrName { + if exists { + return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) + } + id = checkID + exists = true + } else if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) + } + id = checkID + exists = true + } + + return nil + }) + if err != nil { + return err + } else if !exists { + return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName) + } + } + + err = s.getContainerFromDB(id, ctr, ctrConfigBucket, + ctrStateBucket, netNSBucket) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return ctr, nil +} + +// HasContainer checks if a container is present in the state +func (s *BoltState) HasContainer(id string) (bool, error) { + if id == "" { + return false, ErrEmptyID + } + + if !s.valid { + return false, ErrDBClosed + } + + ctrID := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer db.Close() + + exists := false + + err = db.View(func(tx *bolt.Tx) error { + idsBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + ctrExists := idsBucket.Get(ctrID) + if ctrExists != nil { + exists = true + } + + return nil + }) + if err != nil { + return false, err + } + + return exists, nil +} + +// AddContainer adds a container to the state +// The container being added cannot belong to a pod +func (s *BoltState) AddContainer(ctr *Container) error { + if !s.valid { + return ErrDBClosed + } + + if !ctr.valid { + return ErrCtrRemoved + } + + if ctr.config.Pod != "" { + return errors.Wrapf(ErrInvalidArg, "cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod") + } + + // JSON container structs to insert into DB + // TODO use a higher-performance struct encoding than JSON + configJSON, err := json.Marshal(ctr.config) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s config to JSON", ctr.ID()) + } + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID()) + } + netNSPath := "" + if ctr.state.NetNS != nil { + netNSPath = ctr.state.NetNS.Path() + } + + // Collect dependencies for the container. Use a map to ensure no dupes. + dependsCtrs := ctr.Dependencies() + + ctrID := []byte(ctr.ID()) + ctrName := []byte(ctr.Name()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + err = db.Update(func(tx *bolt.Tx) error { + idsBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + ctrConfigBucket, err := getCtrConfigBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + ctrDependsBucket, err := getCtrDependsBucket(tx) + if err != nil { + return err + } + + // Check if we already have a container with the given ID and name + idExist := idsBucket.Get(ctrID) + if idExist != nil { + return errors.Wrapf(ErrCtrExists, "container with ID %s already exists", ctr.ID()) + } + nameExist := namesBucket.Get(ctrName) + if nameExist != nil { + return errors.Wrapf(ErrCtrExists, "container with name %s already exists", ctr.Name()) + } + + // No overlapping containers + // Add the new container to the DB + if err := idsBucket.Put(ctrID, ctrName); err != nil { + return errors.Wrapf(err, "error adding container %s ID to DB", ctr.ID()) + } + 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 err := ctrConfigBucket.Put(ctrID, configJSON); err != nil { + return errors.Wrapf(err, "error adding container %s config to DB", ctr.ID()) + } + if err := ctrStateBucket.Put(ctrID, stateJSON); err != nil { + return errors.Wrapf(err, "error adding container %s state to DB", ctr.ID()) + } + if netNSPath != "" { + if err := netNSBucket.Put(ctrID, []byte(netNSPath)); err != nil { + return errors.Wrapf(err, "error adding container %s netns path to DB", ctr.ID()) + } + } + + // Add dependencies for the container + for _, dependsCtr := range dependsCtrs { + depCtrID := []byte(dependsCtr) + deps := ctrDependsBucket.Get(depCtrID) + depsArray := []string{} + if deps != nil { + if err := json.Unmarshal(deps, &depsArray); err != nil { + return errors.Wrapf(err, "error unmarshalling container %s deps JSON", dependsCtr) + } + } + depsArray = append(depsArray, ctr.ID()) + depsJSON, err := json.Marshal(&depsArray) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s deps JSON", dependsCtr) + } + if err := ctrDependsBucket.Put(depCtrID, depsJSON); err != nil { + return errors.Wrapf(err, "error adding container %s dependencies JSON to DB", dependsCtr) + } + } + + return nil + }) + return err +} + +// RemoveContainer removes a container from the state +// The container will only be removed from the state and not any pods it belongs +// to +func (s *BoltState) RemoveContainer(ctr *Container) error { + if !s.valid { + return ErrDBClosed + } + + ctrID := []byte(ctr.ID()) + ctrName := []byte(ctr.Name()) + + depCtrs := ctr.Dependencies() + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + err = db.Update(func(tx *bolt.Tx) error { + idsBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + ctrConfigBucket, err := getCtrConfigBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + ctrDependsBucket, err := getCtrDependsBucket(tx) + if err != nil { + return err + } + + // Does the container exist? + ctrExists := idsBucket.Get(ctrID) + if ctrExists == nil { + return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID()) + } + + // Does the container have dependencies? + ctrDeps := ctrDependsBucket.Get(ctrID) + if ctrDeps != nil { + dependsCtrs := []string{} + if err := json.Unmarshal(ctrDeps, &dependsCtrs); err != nil { + return errors.Wrapf(err, "cannot unmarshal container %s dependencies JSON", ctr.ID()) + } + if len(dependsCtrs) > 0 { + depsStr := strings.Join(dependsCtrs, ", ") + + return errors.Wrapf(ErrCtrExists, "container %s is a dependency on the following containers: %s", ctr.ID(), depsStr) + } + } + + if err := idsBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s ID in DB", ctr.ID()) + } + + if err := namesBucket.Delete(ctrName); err != nil { + return errors.Wrapf(err, "error deleting container %s name in DB", ctr.ID()) + } + + if err := ctrConfigBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s config in DB", ctr.ID()) + } + + if err := ctrStateBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s state in DB", ctr.ID()) + } + + // Can safely delete netNS even if it doesn't exist + // Delete on a non-existent key doesn't error + if err := netNSBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s network ns in DB", ctr.ID()) + } + + // As above, can safely delete even if it doesn't exist + if err := ctrDependsBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s dependencies in DB", ctr.ID()) + } + + // Remove us from other container's dependencies + for _, depCtr := range depCtrs { + depCtrID := []byte(depCtr) + dep := ctrDependsBucket.Get(depCtrID) + if dep == nil { + // Inconsistent state, but the dependency we were trying to remove doesn't exist... + // Just continue + continue + } + depStr := []string{} + if err := json.Unmarshal(dep, &depStr); err != nil { + return errors.Wrapf(err, "error unmarshaling ctr %s dependencies", ctr.ID(), depCtr) + } + newDeps := make([]string, 0, len(depStr)) + for _, checkID := range depStr { + if checkID != ctr.ID() { + newDeps = append(newDeps, checkID) + } + } + if len(newDeps) == 0 { + // Just delete the container's deps + if err := ctrDependsBucket.Delete(depCtrID); err != nil { + return errors.Wrapf(err, "error deleting container %s dependencies in DB", depCtr) + } + } else { + // Store the new deps + depsJSON, err := json.Marshal(&newDeps) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s dependencies", depCtr) + } + if err := ctrDependsBucket.Put(depCtrID, depsJSON); err != nil { + return errors.Wrapf(err, "error adding container %s dependencies to DB", depCtr) + } + } + } + + return nil + }) + return err +} + +// UpdateContainer updates a container's state from the database +func (s *BoltState) UpdateContainer(ctr *Container) error { + if !s.valid { + return ErrDBClosed + } + + if !ctr.valid { + return ErrCtrRemoved + } + + newState := new(containerState) + netNSPath := "" + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + newStateBytes := ctrStateBucket.Get(ctrID) + if newStateBytes == nil { + ctr.valid = false + return errors.Wrapf(ErrCtrRemoved, "container %s removed from database", ctr.ID()) + } + + if err := json.Unmarshal(newStateBytes, newState); err != nil { + return errors.Wrapf(err, "error unmarshalling container %s state", ctr.ID()) + } + + netNSBytes := netNSBucket.Get(ctrID) + if netNSBytes != nil { + netNSPath = string(netNSBytes) + } + + return nil + }) + if err != nil { + return err + } + + // Do we need to replace the container's netns? + if netNSPath != "" { + // Check if the container's old state has a good netns + if ctr.state.NetNS != nil && netNSPath == ctr.state.NetNS.Path() { + newState.NetNS = ctr.state.NetNS + } else { + // Tear down the existing namespace + if err := s.runtime.teardownNetNS(ctr); err != nil { + return err + } + + // Open the new network namespace + ns, err := joinNetNS(netNSPath) + if err != nil { + return errors.Wrapf(err, "error joining network namespace for container %s", ctr.ID()) + } + newState.NetNS = ns + } + } else { + // The container no longer has a network namespace + // Tear down the old one + if err := s.runtime.teardownNetNS(ctr); err != nil { + return err + } + } + + // New state compiled successfully, swap it into the current state + ctr.state = newState + + return nil +} + +// SaveContainer saves a container's current state in the database +func (s *BoltState) SaveContainer(ctr *Container) error { + if !s.valid { + return ErrDBClosed + } + + if !ctr.valid { + return ErrCtrRemoved + } + + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID()) + } + netNSPath := "" + if ctr.state.NetNS != nil { + netNSPath = ctr.state.NetNS.Path() + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer db.Close() + + err = db.Update(func(tx *bolt.Tx) error { + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + oldJSON := ctrStateBucket.Get(ctrID) + if oldJSON == nil { + ctr.valid = false + return errors.Wrapf(ErrCtrRemoved, "container %s no longer present in DB", ctr.ID()) + } + + // Update the state + if err := ctrStateBucket.Put(ctrID, stateJSON); err != nil { + return errors.Wrapf(err, "error updating container %s state in DB", ctr.ID()) + } + + if netNSPath != "" { + if err := netNSBucket.Put(ctrID, []byte(netNSPath)); err != nil { + return errors.Wrapf(err, "error updating network namespace path for container %s in DB", ctr.ID()) + } + } + + return nil + }) + return err +} + +// ContainerInUse checks if other containers depend on the given container +// It returns a slice of the IDs of the containers depending on the given +// container. If the slice is empty, no containers depend on the given container +func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { + if !s.valid { + return nil, ErrDBClosed + } + + if !ctr.valid { + return nil, ErrCtrRemoved + } + + depCtrs := []string{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + ctrDependsBucket, err := getCtrDependsBucket(tx) + if err != nil { + return err + } + + depsJSON := ctrDependsBucket.Get([]byte(ctr.ID())) + if depsJSON == nil { + // No deps, just return + return nil + } + + // We have deps, un-JSON them + if err := json.Unmarshal(depsJSON, &depCtrs); err != nil { + return errors.Wrapf(err, "error unmarshalling container %s dependencies", ctr.ID()) + } + + return nil + }) + if err != nil { + return nil, err + } + + return depCtrs, nil + +} + +// AllContainers retrieves all the containers in the database +func (s *BoltState) AllContainers() ([]*Container, error) { + if !s.valid { + return nil, ErrDBClosed + } + + ctrs := []*Container{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + ctrConfigBucket, err := getCtrConfigBucket(tx) + if err != nil { + return err + } + + ctrStateBucket, err := getCtrStateBucket(tx) + if err != nil { + return err + } + + netNSBucket, err := getNetNSBucket(tx) + if err != nil { + return err + } + + // Iterate through all containers we know of in the state + // Build a full container struct for all of them and append + // it into the containers listing + err = ctrStateBucket.ForEach(func(id, data []byte) error { + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(containerState) + + err = s.getContainerFromDB(id, ctr, ctrConfigBucket, + ctrStateBucket, netNSBucket) + if err != nil { + return err + } + + ctrs = append(ctrs, ctr) + + return nil + }) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return ctrs, nil +} + +// Pod retrieves a pod given its full ID +func (s *BoltState) Pod(id string) (*Pod, error) { + return nil, ErrNotImplemented +} + +// LookupPod retrieves a pod from full or unique partial ID or name +func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { + return nil, ErrNotImplemented +} + +// HasPod checks if a pod with the given ID exists in the state +func (s *BoltState) HasPod(id string) (bool, error) { + return false, ErrNotImplemented +} + +// PodHasContainer checks if the given pod has a container with the given ID +func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { + return false, ErrNotImplemented +} + +// PodContainersByID returns the IDs of all containers present in the given pod +func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { + return nil, ErrNotImplemented +} + +// PodContainers returns all the containers present in the given pod +func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { + return nil, ErrNotImplemented +} + +// AddPod adds the given pod to the state. Only empty pods can be added. +func (s *BoltState) AddPod(pod *Pod) error { + return ErrNotImplemented +} + +// RemovePod removes the given pod from the state +// Only empty pods can be removed +func (s *BoltState) RemovePod(pod *Pod) error { + return ErrNotImplemented +} + +// RemovePodContainers removes all containers in a pod +func (s *BoltState) RemovePodContainers(pod *Pod) error { + return ErrNotImplemented +} + +// AddContainerToPod adds the given container to an existing pod +// The container will be added to the state and the pod +func (s *BoltState) AddContainerToPod(pod *Pod, ctr *Container) error { + return ErrNotImplemented +} + +// RemoveContainerFromPod removes a container from an existing pod +// The container will also be removed from the state +func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { + return ErrNotImplemented +} + +// AllPods returns all pods present in the state +func (s *BoltState) AllPods() ([]*Pod, error) { + return nil, ErrNotImplemented +} diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go new file mode 100644 index 000000000..8b4e8e28c --- /dev/null +++ b/libpod/boltdb_state_internal.go @@ -0,0 +1,204 @@ +package libpod + +import ( + "encoding/json" + "path/filepath" + + "github.com/boltdb/bolt" + "github.com/containers/storage" + "github.com/pkg/errors" +) + +const ( + idRegistryName = "id-registry" + nameRegistryName = "name-registry" + ctrConfigName = "container-config" + ctrStateName = "container-state" + netNSName = "net-ns" + runtimeConfigName = "runtime-config" + ctrDependsName = "container-depends" +) + +var ( + idRegistryBkt = []byte(idRegistryName) + nameRegistryBkt = []byte(nameRegistryName) + ctrConfigBkt = []byte(ctrConfigName) + ctrStateBkt = []byte(ctrStateName) + netNSBkt = []byte(netNSName) + runtimeConfigBkt = []byte(runtimeConfigName) + ctrDependsBkt = []byte(ctrDependsName) +) + +// Check if the configuration of the database is compatible with the +// configuration of the runtime opening it +// If there is no runtime configuration loaded, load our own +func checkRuntimeConfig(db *bolt.DB, runtime *Runtime) error { + var ( + staticDir = []byte("static-dir") + tmpDir = []byte("tmp-dir") + runRoot = []byte("run-root") + graphRoot = []byte("graph-root") + graphDriverName = []byte("graph-driver-name") + ) + + err := db.Update(func(tx *bolt.Tx) error { + configBkt, err := getRuntimeConfigBucket(tx) + if err != nil { + return err + } + + if err := validateDBAgainstConfig(configBkt, "static dir", + runtime.config.StaticDir, staticDir); err != nil { + return err + } + + if err := validateDBAgainstConfig(configBkt, "tmp dir", + runtime.config.TmpDir, tmpDir); err != nil { + return err + } + + if err := validateDBAgainstConfig(configBkt, "run root", + runtime.config.StorageConfig.RunRoot, runRoot); err != nil { + return err + } + + if err := validateDBAgainstConfig(configBkt, "graph root", + runtime.config.StorageConfig.GraphRoot, graphRoot); err != nil { + return err + } + + return validateDBAgainstConfig(configBkt, "graph driver name", + runtime.config.StorageConfig.GraphDriverName, + graphDriverName) + }) + + return err +} + +// Validate a configuration entry in the DB against current runtime config +// If the given configuration key does not exist it will be created +func validateDBAgainstConfig(bucket *bolt.Bucket, fieldName, runtimeValue string, keyName []byte) error { + keyBytes := bucket.Get(keyName) + if keyBytes == nil { + if err := bucket.Put(keyName, []byte(runtimeValue)); err != nil { + return errors.Wrapf(err, "error updating %s in DB runtime config", fieldName) + } + } else { + if runtimeValue != string(keyBytes) { + return errors.Wrapf(ErrDBBadConfig, "database %s %s does not match our %s %s", + fieldName, string(keyBytes), fieldName, runtimeValue) + } + } + + return nil +} + +func (s *BoltState) getDBCon() (*bolt.DB, error) { + db, err := bolt.Open(s.dbPath, 0600, nil) + if err != nil { + return nil, errors.Wrapf(err, "error opening database %s", s.dbPath) + } + + return db, nil +} + +func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(idRegistryBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "id registry bucket not found in DB") + } + return bkt, nil +} + +func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(nameRegistryBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "name registry bucket not found in DB") + } + return bkt, nil +} + +func getCtrConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(ctrConfigBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "container config bucket not found in DB") + } + return bkt, nil +} + +func getCtrStateBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(ctrStateBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "container state bucket not found in DB") + } + return bkt, nil +} + +func getNetNSBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(netNSBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "network namespace bucket not found in DB") + } + return bkt, nil +} + +func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(runtimeConfigBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "runtime configuration bucket not found in DB") + } + return bkt, nil +} + +func getCtrDependsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(ctrDependsBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "container dependencies bucket not found in DB") + } + return bkt, nil +} + +func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, config, state, netNS *bolt.Bucket) error { + configBytes := config.Get(id) + if configBytes == nil { + return errors.Wrapf(ErrNoSuchCtr, "error unmarshalling container %s config", string(id)) + } + + if err := json.Unmarshal(configBytes, ctr.config); err != nil { + return errors.Wrapf(err, "error unmarshalling container %s config", string(id)) + } + + stateBytes := state.Get(id) + if stateBytes == nil { + return errors.Wrapf(ErrInternal, "container %s has config but no state", string(id)) + } + + if err := json.Unmarshal(stateBytes, ctr.state); err != nil { + return errors.Wrapf(err, "error unmarshalling container %s state", string(id)) + } + + // The container may not have a network namespace, so it's OK if this is + // nil + netNSBytes := netNS.Get(id) + if netNSBytes != nil { + nsPath := string(netNSBytes) + netNS, err := joinNetNS(nsPath) + if err != nil { + return errors.Wrapf(err, "error joining network namespace for container %s", string(id)) + } + ctr.state.NetNS = netNS + } + + // Get the lock + lockPath := filepath.Join(s.lockDir, string(id)) + lock, err := storage.GetLockfile(lockPath) + if err != nil { + return errors.Wrapf(err, "error retrieving lockfile for container %s", string(id)) + } + ctr.lock = lock + + ctr.runtime = s.runtime + ctr.valid = true + + return nil +} diff --git a/libpod/container.go b/libpod/container.go index 06dca823a..4d28c1b59 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -135,11 +135,11 @@ type containerState struct { // NetNSPath is the path of the container's network namespace // Will only be set if config.CreateNetNS is true, or the container was // told to join another container's network namespace - NetNS ns.NetNS + NetNS ns.NetNS `json:"-"` // IP address of container (if network namespace was created) - IPAddress string + IPAddress string `json:"ipAddress"` // Subnet mask of container (if network namespace was created) - SubnetMask string + SubnetMask string `json:"subnetMask"` } // ContainerConfig contains all information that was used to create the diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 8aa2360ba..c99a34e7a 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -481,10 +481,10 @@ func (s *InMemoryState) RemovePodContainers(pod *Pod) error { // state func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { if !pod.valid { - return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and cannot be added to", pod.ID()) + return errors.Wrapf(ErrPodRemoved, "pod %s is not valid", pod.ID()) } if !ctr.valid { - return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be added to the pod", ctr.ID()) + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", ctr.ID()) } if ctr.config.Pod != pod.ID() { diff --git a/libpod/runtime.go b/libpod/runtime.go index cce396764..6e26b0f1b 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -27,6 +27,9 @@ const ( InMemoryStateStore RuntimeStateStore = iota // SQLiteStateStore is a state backed by a SQLite database SQLiteStateStore RuntimeStateStore = iota + // BoltDBStateStore is a state backed by a BoltDB database + BoltDBStateStore RuntimeStateStore = iota + // SeccompDefaultPath defines the default seccomp path SeccompDefaultPath = "/usr/share/containers/seccomp.json" // SeccompOverridePath if this exists it overrides the default seccomp path @@ -76,7 +79,7 @@ var ( // Leave this empty so containers/storage will use its defaults StorageConfig: storage.StoreOptions{}, ImageDefaultTransport: DefaultTransport, - StateType: SQLiteStateStore, + StateType: BoltDBStateStore, RuntimePath: findRuncPath(), ConmonPath: findConmonPath(), ConmonEnvVars: []string{ @@ -235,6 +238,14 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { return nil, err } runtime.state = state + case BoltDBStateStore: + dbPath := filepath.Join(runtime.config.StaticDir, "bolt_state.db") + + state, err := NewBoltState(dbPath, runtime.lockDir, runtime) + if err != nil { + return nil, err + } + runtime.state = state default: return nil, errors.Wrapf(ErrInvalidArg, "unrecognized state type passed") } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index e1c80772e..4f8587186 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -201,7 +201,7 @@ func (r *Runtime) removeContainer(c *Container, force bool) error { // we haven't been created in the runtime yet if c.state.State != ContainerStateConfigured { if err := r.ociRuntime.deleteContainer(c); err != nil { - return errors.Wrapf(err, "error removing container %s from runc", c.ID()) + return errors.Wrapf(err, "error removing container %s from OCI runtime", c.ID()) } } diff --git a/libpod/sql_state.go b/libpod/sql_state.go index f67969cf7..d69a3b58e 100644 --- a/libpod/sql_state.go +++ b/libpod/sql_state.go @@ -49,9 +49,6 @@ func NewSQLState(dbPath, specsDir, lockDir string, runtime *Runtime) (State, err } state.lockDir = lockDir - // TODO add a separate temporary database for per-boot container - // state - // Open the database // Use loc=auto to get accurate locales for timestamps db, err := sql.Open("sqlite3", dbPath+"?_loc=auto") diff --git a/libpod/state_test.go b/libpod/state_test.go index b98674134..63e3bf173 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -26,9 +26,37 @@ var ( testedStates = map[string]emptyStateFunc{ "sql": getEmptySQLState, "in-memory": getEmptyInMemoryState, + "boltdb": getEmptyBoltState, } ) +// Get an empty BoltDB state for use in tests +func getEmptyBoltState() (s State, p string, p2 string, err error) { + tmpDir, err := ioutil.TempDir("", tmpDirPrefix) + if err != nil { + return nil, "", "", err + } + defer func() { + if err != nil { + os.RemoveAll(tmpDir) + } + }() + + dbPath := filepath.Join(tmpDir, "db.sql") + lockDir := filepath.Join(tmpDir, "locks") + + runtime := new(Runtime) + runtime.config = new(RuntimeConfig) + runtime.config.StorageConfig = storage.StoreOptions{} + + state, err := NewBoltState(dbPath, lockDir, runtime) + if err != nil { + return nil, "", "", err + } + + return state, tmpDir, lockDir, nil +} + // Get an empty in-memory state for use in tests func getEmptyInMemoryState() (s State, p string, p2 string, err error) { tmpDir, err := ioutil.TempDir("", tmpDirPrefix) |