diff options
Diffstat (limited to 'libpod/boltdb_state.go')
-rw-r--r-- | libpod/boltdb_state.go | 632 |
1 files changed, 456 insertions, 176 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 9745121c7..81f11410b 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -2,16 +2,18 @@ package libpod import ( "bytes" + "errors" "fmt" "net" "os" + "strconv" "strings" "sync" + "time" "github.com/containers/common/libnetwork/types" "github.com/containers/podman/v4/libpod/define" jsoniter "github.com/json-iterator/go" - "github.com/pkg/errors" "github.com/sirupsen/logrus" bolt "go.etcd.io/bbolt" ) @@ -63,6 +65,13 @@ type BoltState struct { // initially created the database. This must match for any further instances // that access the database, to ensure that state mismatches with // containers/storage do not occur. +// - exitCodeBucket/exitCodeTimeStampBucket: (#14559) exit codes must be part +// of the database to resolve a previous race condition when one process waits +// for the exit file to be written and another process removes it along with +// the container during auto-removal. The same race would happen trying to +// read the exit code from the containers bucket. Hence, exit codes go into +// their own bucket. To avoid the rather expensive JSON (un)marshaling, we +// have two buckets: one for the exit codes, the other for the timestamps. // NewBoltState creates a new bolt-backed state database func NewBoltState(path string, runtime *Runtime) (State, error) { @@ -76,7 +85,7 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { db, err := bolt.Open(path, 0600, nil) if err != nil { - return nil, errors.Wrapf(err, "error opening database %s", path) + return nil, fmt.Errorf("error opening database %s: %w", path, err) } // Everywhere else, we use s.deferredCloseDBCon(db) to ensure the state's DB // mutex is also unlocked. @@ -98,6 +107,8 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { allVolsBkt, execBkt, runtimeConfigBkt, + exitCodeBkt, + exitCodeTimeStampBkt, } // Does the DB need an update? @@ -112,7 +123,7 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { return nil }) if err != nil { - return nil, errors.Wrapf(err, "error checking DB schema") + return nil, fmt.Errorf("error checking DB schema: %w", err) } if !needsUpdate { @@ -124,13 +135,13 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { err = db.Update(func(tx *bolt.Tx) error { for _, bkt := range createBuckets { if _, err := tx.CreateBucketIfNotExists(bkt); err != nil { - return errors.Wrapf(err, "error creating bucket %s", string(bkt)) + return fmt.Errorf("error creating bucket %s: %w", string(bkt), err) } } return nil }) if err != nil { - return nil, errors.Wrapf(err, "error creating buckets for DB") + return nil, fmt.Errorf("error creating buckets for DB: %w", err) } state.valid = true @@ -162,6 +173,11 @@ func (s *BoltState) Refresh() error { return err } + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + ctrsBucket, err := getCtrBucket(tx) if err != nil { return err @@ -187,11 +203,51 @@ func (s *BoltState) Refresh() error { return err } + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + // Clear all exec exit codes + toRemoveExitCodes := []string{} + err = exitCodeBucket.ForEach(func(id, _ []byte) error { + toRemoveExitCodes = append(toRemoveExitCodes, string(id)) + return nil + }) + if err != nil { + return fmt.Errorf("error reading exit codes bucket: %w", err) + } + for _, id := range toRemoveExitCodes { + if err := exitCodeBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("error removing exit code for ID %s: %w", id, err) + } + } + + toRemoveTimeStamps := []string{} + err = timeStampBucket.ForEach(func(id, _ []byte) error { + toRemoveTimeStamps = append(toRemoveTimeStamps, string(id)) + return nil + }) + if err != nil { + return fmt.Errorf("reading timestamps bucket: %w", err) + } + for _, id := range toRemoveTimeStamps { + if err := timeStampBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("removing timestamp for ID %s: %w", id, 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 + toRemoveIDs := []string{} err = idBucket.ForEach(func(id, name []byte) error { ctrBkt := ctrsBucket.Bucket(id) if ctrBkt == nil { @@ -199,20 +255,28 @@ func (s *BoltState) Refresh() error { podBkt := podsBucket.Bucket(id) if podBkt == nil { // This is neither a pod nor a container - // Error out on the dangling ID - return errors.Wrapf(define.ErrInternal, "id %s is not a pod or a container", string(id)) + // Something is seriously wrong, but + // continue on and try to clean up the + // state and become consistent. + // Just note what needs to be removed + // for now - ForEach says you shouldn't + // remove things from the table during + // it. + logrus.Errorf("Database issue: dangling ID %s found (not a pod or container) - removing", string(id)) + toRemoveIDs = append(toRemoveIDs, string(id)) + return nil } // Get the state stateBytes := podBkt.Get(stateKey) if stateBytes == nil { - return errors.Wrapf(define.ErrInternal, "pod %s missing state key", string(id)) + return fmt.Errorf("pod %s missing state key: %w", string(id), define.ErrInternal) } state := new(podState) if err := json.Unmarshal(stateBytes, state); err != nil { - return errors.Wrapf(err, "error unmarshalling state for pod %s", string(id)) + return fmt.Errorf("error unmarshalling state for pod %s: %w", string(id), err) } // Clear the Cgroup path @@ -220,11 +284,11 @@ func (s *BoltState) Refresh() error { newStateBytes, err := json.Marshal(state) if err != nil { - return errors.Wrapf(err, "error marshalling modified state for pod %s", string(id)) + return fmt.Errorf("error marshalling modified state for pod %s: %w", string(id), err) } if err := podBkt.Put(stateKey, newStateBytes); err != nil { - return errors.Wrapf(err, "error updating state for pod %s in DB", string(id)) + return fmt.Errorf("error updating state for pod %s in DB: %w", string(id), err) } // It's not a container, nothing to do @@ -233,30 +297,30 @@ func (s *BoltState) Refresh() error { // First, delete the network namespace if err := ctrBkt.Delete(netNSKey); err != nil { - return errors.Wrapf(err, "error removing network namespace for container %s", string(id)) + return fmt.Errorf("error removing network namespace for container %s: %w", string(id), err) } stateBytes := ctrBkt.Get(stateKey) if stateBytes == nil { // Badly formatted container bucket - return errors.Wrapf(define.ErrInternal, "container %s missing state in DB", string(id)) + return fmt.Errorf("container %s missing state in DB: %w", string(id), define.ErrInternal) } state := new(ContainerState) if err := json.Unmarshal(stateBytes, state); err != nil { - return errors.Wrapf(err, "error unmarshalling state for container %s", string(id)) + return fmt.Errorf("error unmarshalling state for container %s: %w", string(id), err) } resetState(state) newStateBytes, err := json.Marshal(state) if err != nil { - return errors.Wrapf(err, "error marshalling modified state for container %s", string(id)) + return fmt.Errorf("error marshalling modified state for container %s: %w", string(id), err) } if err := ctrBkt.Put(stateKey, newStateBytes); err != nil { - return errors.Wrapf(err, "error updating state for container %s in DB", string(id)) + return fmt.Errorf("error updating state for container %s in DB: %w", string(id), err) } // Delete all exec sessions, if there are any @@ -274,7 +338,7 @@ func (s *BoltState) Refresh() error { } for _, execID := range toRemove { if err := ctrExecBkt.Delete([]byte(execID)); err != nil { - return errors.Wrapf(err, "error removing exec session %s from container %s", execID, string(id)) + return fmt.Errorf("error removing exec session %s from container %s: %w", execID, string(id), err) } } } @@ -285,11 +349,29 @@ func (s *BoltState) Refresh() error { return err } + // Remove dangling IDs. + for _, id := range toRemoveIDs { + // Look up the ID to see if we also have a dangling name + // in the DB. + name := idBucket.Get([]byte(id)) + if name != nil { + if testID := namesBucket.Get(name); testID != nil { + logrus.Infof("Found dangling name %s (ID %s) in database", string(name), id) + if err := namesBucket.Delete(name); err != nil { + return fmt.Errorf("error removing dangling name %s (ID %s) from database: %w", string(name), id, err) + } + } + } + if err := idBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("error removing dangling ID %s from database: %w", id, err) + } + } + // Now refresh volumes err = allVolsBucket.ForEach(func(id, name []byte) error { dbVol := volBucket.Bucket(id) if dbVol == nil { - return errors.Wrapf(define.ErrInternal, "inconsistency in state - volume %s is in all volumes bucket but volume not found", string(id)) + return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal) } // Get the state @@ -302,7 +384,7 @@ func (s *BoltState) Refresh() error { oldState := new(VolumeState) if err := json.Unmarshal(volStateBytes, oldState); err != nil { - return errors.Wrapf(err, "error unmarshalling state for volume %s", string(id)) + return fmt.Errorf("error unmarshalling state for volume %s: %w", string(id), err) } // Reset mount count to 0 @@ -311,11 +393,11 @@ func (s *BoltState) Refresh() error { newState, err := json.Marshal(oldState) if err != nil { - return errors.Wrapf(err, "error marshalling state for volume %s", string(id)) + return fmt.Errorf("error marshalling state for volume %s: %w", string(id), err) } if err := dbVol.Put(stateKey, newState); err != nil { - return errors.Wrapf(err, "error storing new state for volume %s", string(id)) + return fmt.Errorf("error storing new state for volume %s: %w", string(id), err) } return nil @@ -339,7 +421,7 @@ func (s *BoltState) Refresh() error { for _, execSession := range toRemoveExec { if err := execBucket.Delete([]byte(execSession)); err != nil { - return errors.Wrapf(err, "error deleting exec session %s registry from database", execSession) + return fmt.Errorf("error deleting exec session %s registry from database: %w", execSession, err) } } @@ -561,7 +643,7 @@ func (s *BoltState) LookupContainerID(idOrName string) (string, error) { if s.namespaceBytes != nil { ns := nsBucket.Get(fullID) if !bytes.Equal(ns, s.namespaceBytes) { - return errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + return fmt.Errorf("no container found with name or ID %s: %w", idOrName, define.ErrNoSuchCtr) } } id = fullID @@ -686,7 +768,7 @@ func (s *BoltState) AddContainer(ctr *Container) error { } if ctr.config.Pod != "" { - return errors.Wrapf(define.ErrInvalidArg, "cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod") + return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg) } return s.addContainer(ctr, nil) @@ -701,7 +783,7 @@ func (s *BoltState) RemoveContainer(ctr *Container) error { } if ctr.config.Pod != "" { - return errors.Wrapf(define.ErrPodExists, "container %s is part of a pod, use RemoveContainerFromPod instead", ctr.ID()) + return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists) } db, err := s.getDBCon() @@ -727,7 +809,7 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } newState := new(ContainerState) @@ -750,16 +832,16 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { ctrToUpdate := ctrBucket.Bucket(ctrID) if ctrToUpdate == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) } newStateBytes := ctrToUpdate.Get(stateKey) if newStateBytes == nil { - return errors.Wrapf(define.ErrInternal, "container %s does not have a state key in DB", ctr.ID()) + return fmt.Errorf("container %s does not have a state key in DB: %w", ctr.ID(), define.ErrInternal) } if err := json.Unmarshal(newStateBytes, newState); err != nil { - return errors.Wrapf(err, "error unmarshalling container %s state", ctr.ID()) + return fmt.Errorf("error unmarshalling container %s state: %w", ctr.ID(), err) } netNSBytes := ctrToUpdate.Get(netNSKey) @@ -799,12 +881,12 @@ func (s *BoltState) SaveContainer(ctr *Container) error { } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } stateJSON, err := json.Marshal(ctr.state) if err != nil { - return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID()) + return fmt.Errorf("error marshalling container %s state to JSON: %w", ctr.ID(), err) } netNSPath := getNetNSPath(ctr) @@ -825,22 +907,22 @@ func (s *BoltState) SaveContainer(ctr *Container) error { ctrToSave := ctrBucket.Bucket(ctrID) if ctrToSave == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in DB", ctr.ID()) + return fmt.Errorf("container %s does not exist in DB: %w", ctr.ID(), define.ErrNoSuchCtr) } // Update the state if err := ctrToSave.Put(stateKey, stateJSON); err != nil { - return errors.Wrapf(err, "error updating container %s state in DB", ctr.ID()) + return fmt.Errorf("error updating container %s state in DB: %w", ctr.ID(), err) } if netNSPath != "" { if err := ctrToSave.Put(netNSKey, []byte(netNSPath)); err != nil { - return errors.Wrapf(err, "error updating network namespace path for container %s in DB", ctr.ID()) + return fmt.Errorf("error updating network namespace path for container %s in DB: %w", ctr.ID(), err) } } else { // Delete the existing network namespace if err := ctrToSave.Delete(netNSKey); err != nil { - return errors.Wrapf(err, "error removing network namespace path for container %s in DB", ctr.ID()) + return fmt.Errorf("error removing network namespace path for container %s in DB: %w", ctr.ID(), err) } } @@ -862,7 +944,7 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return nil, fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } depCtrs := []string{} @@ -882,12 +964,12 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { ctrDB := ctrBucket.Bucket([]byte(ctr.ID())) if ctrDB == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID %q found in DB", ctr.ID()) + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) } dependsBkt := ctrDB.Bucket(dependenciesBkt) if dependsBkt == nil { - return errors.Wrapf(define.ErrInternal, "container %s has no dependencies bucket", ctr.ID()) + return fmt.Errorf("container %s has no dependencies bucket: %w", ctr.ID(), define.ErrInternal) } // Iterate through and add dependencies @@ -940,7 +1022,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) { // be much less helpful. ctrExists := ctrBucket.Bucket(id) if ctrExists == nil { - return errors.Wrapf(define.ErrInternal, "state is inconsistent - container ID %s in all containers, but container not found", string(id)) + return fmt.Errorf("state is inconsistent - container ID %s in all containers, but container not found: %w", string(id), define.ErrInternal) } ctr := new(Container) @@ -952,7 +1034,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) { // ignore it safely. // We just won't include the container in the // results. - if errors.Cause(err) != define.ErrNSMismatch { + if !errors.Is(err, define.ErrNSMismatch) { // Even if it's not an NS mismatch, it's // not worth erroring over. // If we do, a single bad container JSON @@ -984,7 +1066,7 @@ func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOpti } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return nil, fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } // if the network mode is not bridge return no networks @@ -1013,7 +1095,7 @@ func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOpti dbCtr := ctrBucket.Bucket(ctrID) if dbCtr == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) } ctrNetworkBkt := dbCtr.Bucket(networksBkt) @@ -1051,7 +1133,7 @@ func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOpti dbCtr := ctrBucket.Bucket(ctrID) if dbCtr == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) } var networkList []string @@ -1060,7 +1142,7 @@ func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOpti if ctrNetworkBkt == nil { ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt) if err != nil { - return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) + return fmt.Errorf("error creating networks bucket for container %s: %w", ctr.ID(), err) } // the container has no networks in the db lookup config and write to the db networkList = ctr.config.NetworksDeprecated @@ -1084,7 +1166,7 @@ func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOpti if ctr.state.NetInterfaceDescriptions != nil { eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network) if !exists { - return errors.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) + return fmt.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) } intName = eth } else { @@ -1158,16 +1240,16 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.Pe } if network == "" { - return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } optBytes, err := json.Marshal(opts) if err != nil { - return errors.Wrapf(err, "error marshalling network options JSON for container %s", ctr.ID()) + return fmt.Errorf("error marshalling network options JSON for container %s: %w", ctr.ID(), err) } ctrID := []byte(ctr.ID()) @@ -1187,21 +1269,21 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.Pe dbCtr := ctrBucket.Bucket(ctrID) if dbCtr == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) } ctrNetworksBkt := dbCtr.Bucket(networksBkt) if ctrNetworksBkt == nil { - return errors.Wrapf(define.ErrNoSuchNetwork, "container %s does not have a network bucket", ctr.ID()) + return fmt.Errorf("container %s does not have a network bucket: %w", ctr.ID(), define.ErrNoSuchNetwork) } netConnected := ctrNetworksBkt.Get([]byte(network)) if netConnected != nil { - return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to network %q", ctr.ID(), network) + return fmt.Errorf("container %s is already connected to network %q: %w", ctr.ID(), network, define.ErrNetworkExists) } // Add the network if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil { - return errors.Wrapf(err, "error adding container %s to network %s in DB", ctr.ID(), network) + return fmt.Errorf("error adding container %s to network %s in DB: %w", ctr.ID(), network, err) } return nil @@ -1220,11 +1302,11 @@ func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { } if network == "" { - return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) } if s.namespace != "" && s.namespace != ctr.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return fmt.Errorf("container %s is in namespace %q, does not match our namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } ctrID := []byte(ctr.ID()) @@ -1244,21 +1326,21 @@ func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { dbCtr := ctrBucket.Bucket(ctrID) if dbCtr == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) } ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) ctrNetworksBkt := dbCtr.Bucket(networksBkt) if ctrNetworksBkt == nil { - return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not connected to any CNI networks, so cannot disconnect", ctr.ID()) + return fmt.Errorf("container %s is not connected to any CNI networks, so cannot disconnect: %w", ctr.ID(), define.ErrNoSuchNetwork) } netConnected := ctrNetworksBkt.Get([]byte(network)) if netConnected == nil { - return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not connected to CNI network %q", ctr.ID(), network) + return fmt.Errorf("container %s is not connected to CNI network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork) } if err := ctrNetworksBkt.Delete([]byte(network)); err != nil { - return errors.Wrapf(err, "error removing container %s from network %s", ctr.ID(), network) + return fmt.Errorf("error removing container %s from network %s: %w", ctr.ID(), network, err) } if ctrAliasesBkt != nil { @@ -1268,7 +1350,7 @@ func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { } if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil { - return errors.Wrapf(err, "error removing container %s network aliases for network %s", ctr.ID(), network) + return fmt.Errorf("error removing container %s network aliases for network %s: %w", ctr.ID(), network, err) } } @@ -1309,6 +1391,204 @@ func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { return config, nil } +// AddContainerExitCode adds the exit code for the specified container to the database. +func (s *BoltState) AddContainerExitCode(id string, exitCode int32) error { + if len(id) == 0 { + return define.ErrEmptyID + } + + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + rawExitCode := []byte(strconv.Itoa(int(exitCode))) + rawTimeStamp, err := time.Now().MarshalText() + if err != nil { + return fmt.Errorf("marshaling exit-code time stamp: %w", err) + } + + return db.Update(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + if err := exitCodeBucket.Put(rawID, rawExitCode); err != nil { + return fmt.Errorf("adding exit code of container %s to DB: %w", id, err) + } + if err := timeStampBucket.Put(rawID, rawTimeStamp); err != nil { + if rmErr := exitCodeBucket.Delete(rawID); rmErr != nil { + logrus.Errorf("Removing exit code of container %s from DB: %v", id, rmErr) + } + return fmt.Errorf("adding exit-code time stamp of container %s to DB: %w", id, err) + } + + return nil + }) +} + +// GetContainerExitCode returns the exit code for the specified container. +func (s *BoltState) GetContainerExitCode(id string) (int32, error) { + if len(id) == 0 { + return -1, define.ErrEmptyID + } + + if !s.valid { + return -1, define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return -1, err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + result := int32(-1) + return result, db.View(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + + rawExitCode := exitCodeBucket.Get(rawID) + if rawExitCode == nil { + return fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + + exitCode, err := strconv.Atoi(string(rawExitCode)) + if err != nil { + return fmt.Errorf("converting raw exit code %v of container %s: %w", rawExitCode, id, err) + } + + result = int32(exitCode) + return nil + }) +} + +// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of +// the specified container was added to the database. +func (s *BoltState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + var result time.Time + return &result, db.View(func(tx *bolt.Tx) error { + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + rawTimeStamp := timeStampBucket.Get(rawID) + if rawTimeStamp == nil { + return fmt.Errorf("getting exit-code time stamp of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + + if err := result.UnmarshalText(rawTimeStamp); err != nil { + return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, id, err) + } + + return nil + }) +} + +// PruneExitCodes removes exit codes older than 5 minutes. +func (s *BoltState) PruneContainerExitCodes() error { + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + toRemoveIDs := []string{} + + threshold := time.Minute * 5 + err = db.View(func(tx *bolt.Tx) error { + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error { + var timeStamp time.Time + if err := timeStamp.UnmarshalText(rawTimeStamp); err != nil { + return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, string(rawID), err) + } + if time.Since(timeStamp) > threshold { + toRemoveIDs = append(toRemoveIDs, string(rawID)) + } + return nil + }) + }) + if err != nil { + return fmt.Errorf("reading exit codes to prune: %w", err) + } + + if len(toRemoveIDs) > 0 { + err = db.Update(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + var finalErr error + for _, id := range toRemoveIDs { + rawID := []byte(id) + if err := exitCodeBucket.Delete(rawID); err != nil { + if finalErr != nil { + logrus.Error(finalErr) + } + finalErr = fmt.Errorf("removing exit code of container %s from DB: %w", id, err) + } + if err := timeStampBucket.Delete(rawID); err != nil { + if finalErr != nil { + logrus.Error(finalErr) + } + finalErr = fmt.Errorf("removing exit code timestamp of container %s from DB: %w", id, err) + } + } + + return finalErr + }) + if err != nil { + return fmt.Errorf("pruning exit codes: %w", err) + } + } + + return nil +} + // AddExecSession adds an exec session to the state. func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error { if !s.valid { @@ -1341,25 +1621,25 @@ func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error { dbCtr := ctrBucket.Bucket(ctrID) if dbCtr == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s is not present in the database", ctr.ID()) + return fmt.Errorf("container %s is not present in the database: %w", ctr.ID(), define.ErrNoSuchCtr) } ctrExecSessionBucket, err := dbCtr.CreateBucketIfNotExists(execBkt) if err != nil { - return errors.Wrapf(err, "error creating exec sessions bucket for container %s", ctr.ID()) + return fmt.Errorf("error creating exec sessions bucket for container %s: %w", ctr.ID(), err) } execExists := execBucket.Get(sessionID) if execExists != nil { - return errors.Wrapf(define.ErrExecSessionExists, "an exec session with ID %s already exists", session.ID()) + return fmt.Errorf("an exec session with ID %s already exists: %w", session.ID(), define.ErrExecSessionExists) } if err := execBucket.Put(sessionID, ctrID); err != nil { - return errors.Wrapf(err, "error adding exec session %s to DB", session.ID()) + return fmt.Errorf("error adding exec session %s to DB: %w", session.ID(), err) } if err := ctrExecSessionBucket.Put(sessionID, ctrID); err != nil { - return errors.Wrapf(err, "error adding exec session %s to container %s in DB", session.ID(), ctr.ID()) + return fmt.Errorf("error adding exec session %s to container %s in DB: %w", session.ID(), ctr.ID(), err) } return nil @@ -1393,7 +1673,7 @@ func (s *BoltState) GetExecSession(id string) (string, error) { ctr := execBucket.Get([]byte(id)) if ctr == nil { - return errors.Wrapf(define.ErrNoSuchExecSession, "no exec session with ID %s found", id) + return fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession) } ctrID = string(ctr) return nil @@ -1432,11 +1712,11 @@ func (s *BoltState) RemoveExecSession(session *ExecSession) error { } // Check that container ID matches if string(sessionExists) != session.ContainerID() { - return errors.Wrapf(define.ErrInternal, "database inconsistency: exec session %s points to container %s in state but %s in database", session.ID(), session.ContainerID(), string(sessionExists)) + return fmt.Errorf("database inconsistency: exec session %s points to container %s in state but %s in database: %w", session.ID(), session.ContainerID(), string(sessionExists), define.ErrInternal) } if err := execBucket.Delete(sessionID); err != nil { - return errors.Wrapf(err, "error removing exec session %s from database", session.ID()) + return fmt.Errorf("error removing exec session %s from database: %w", session.ID(), err) } dbCtr := ctrBucket.Bucket(containerID) @@ -1459,7 +1739,7 @@ func (s *BoltState) RemoveExecSession(session *ExecSession) error { ctrSessionExists := ctrExecBucket.Get(sessionID) if ctrSessionExists != nil { if err := ctrExecBucket.Delete(sessionID); err != nil { - return errors.Wrapf(err, "error removing exec session %s from container %s in database", session.ID(), session.ContainerID()) + return fmt.Errorf("error removing exec session %s from container %s in database: %w", session.ID(), session.ContainerID(), err) } } @@ -1567,7 +1847,7 @@ func (s *BoltState) RemoveContainerExecSessions(ctr *Container) error { for _, session := range sessions { if err := ctrExecSessions.Delete([]byte(session)); err != nil { - return errors.Wrapf(err, "error removing container %s exec session %s from database", ctr.ID(), session) + return fmt.Errorf("error removing container %s exec session %s from database: %w", ctr.ID(), session, err) } // Check if the session exists in the global table // before removing. It should, but in cases where the DB @@ -1578,10 +1858,10 @@ func (s *BoltState) RemoveContainerExecSessions(ctr *Container) error { continue } if string(sessionExists) != ctr.ID() { - return errors.Wrapf(define.ErrInternal, "database mismatch: exec session %s is associated with containers %s and %s", session, ctr.ID(), string(sessionExists)) + return fmt.Errorf("database mismatch: exec session %s is associated with containers %s and %s: %w", session, ctr.ID(), string(sessionExists), define.ErrInternal) } if err := execBucket.Delete([]byte(session)); err != nil { - return errors.Wrapf(err, "error removing container %s exec session %s from exec sessions", ctr.ID(), session) + return fmt.Errorf("error removing container %s exec session %s from exec sessions: %w", ctr.ID(), session, err) } } @@ -1604,7 +1884,7 @@ func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConf newCfgJSON, err := json.Marshal(newCfg) if err != nil { - return errors.Wrapf(err, "error marshalling new configuration JSON for container %s", ctr.ID()) + return fmt.Errorf("error marshalling new configuration JSON for container %s: %w", ctr.ID(), err) } db, err := s.getDBCon() @@ -1622,11 +1902,11 @@ func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConf ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) if ctrDB == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID %q found in DB", ctr.ID()) + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) } if err := ctrDB.Put(configKey, newCfgJSON); err != nil { - return errors.Wrapf(err, "error updating container %s config JSON", ctr.ID()) + return fmt.Errorf("error updating container %s config JSON: %w", ctr.ID(), err) } return nil @@ -1649,15 +1929,15 @@ func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName } if newName != "" && newCfg.Name != newName { - return errors.Wrapf(define.ErrInvalidArg, "new name %s for container %s must match name in given container config", newName, ctr.ID()) + return fmt.Errorf("new name %s for container %s must match name in given container config: %w", newName, ctr.ID(), define.ErrInvalidArg) } if newName != "" && oldName == "" { - return errors.Wrapf(define.ErrInvalidArg, "must provide old name for container if a new name is given") + return fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg) } newCfgJSON, err := json.Marshal(newCfg) if err != nil { - return errors.Wrapf(err, "error marshalling new configuration JSON for container %s", ctr.ID()) + return fmt.Errorf("error marshalling new configuration JSON for container %s: %w", ctr.ID(), err) } db, err := s.getDBCon() @@ -1689,7 +1969,7 @@ func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName // rename. needsRename = false } else { - return errors.Wrapf(define.ErrCtrExists, "name %s already in use, cannot rename container %s", newName, ctr.ID()) + return fmt.Errorf("name %s already in use, cannot rename container %s: %w", newName, ctr.ID(), define.ErrCtrExists) } } @@ -1698,16 +1978,16 @@ func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName // buckets are ID-indexed so we just need to // overwrite the values there. if err := namesBkt.Delete([]byte(oldName)); err != nil { - return errors.Wrapf(err, "error deleting container %s old name from DB for rename", ctr.ID()) + return fmt.Errorf("error deleting container %s old name from DB for rename: %w", ctr.ID(), err) } if err := idBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { - return errors.Wrapf(err, "error renaming container %s in ID bucket in DB", ctr.ID()) + return fmt.Errorf("error renaming container %s in ID bucket in DB: %w", ctr.ID(), err) } if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil { - return errors.Wrapf(err, "error adding new name %s for container %s in DB", newName, ctr.ID()) + return fmt.Errorf("error adding new name %s for container %s in DB: %w", newName, ctr.ID(), err) } if err := allCtrsBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { - return errors.Wrapf(err, "error renaming container %s in all containers bucket in DB", ctr.ID()) + return fmt.Errorf("error renaming container %s in all containers bucket in DB: %w", ctr.ID(), err) } if ctr.config.Pod != "" { podsBkt, err := getPodBucket(tx) @@ -1716,14 +1996,14 @@ func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName } podBkt := podsBkt.Bucket([]byte(ctr.config.Pod)) if podBkt == nil { - return errors.Wrapf(define.ErrInternal, "bucket for pod %s does not exist", ctr.config.Pod) + return fmt.Errorf("bucket for pod %s does not exist: %w", ctr.config.Pod, define.ErrInternal) } podCtrBkt := podBkt.Bucket(containersBkt) if podCtrBkt == nil { - return errors.Wrapf(define.ErrInternal, "pod %s does not have a containers bucket", ctr.config.Pod) + return fmt.Errorf("pod %s does not have a containers bucket: %w", ctr.config.Pod, define.ErrInternal) } if err := podCtrBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { - return errors.Wrapf(err, "error renaming container %s in pod %s members bucket", ctr.ID(), ctr.config.Pod) + return fmt.Errorf("error renaming container %s in pod %s members bucket: %w", ctr.ID(), ctr.config.Pod, err) } } } @@ -1737,11 +2017,11 @@ func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) if ctrDB == nil { ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID %q found in DB", ctr.ID()) + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) } if err := ctrDB.Put(configKey, newCfgJSON); err != nil { - return errors.Wrapf(err, "error updating container %s config JSON", ctr.ID()) + return fmt.Errorf("error updating container %s config JSON: %w", ctr.ID(), err) } return nil @@ -1763,7 +2043,7 @@ func (s *BoltState) RewritePodConfig(pod *Pod, newCfg *PodConfig) error { newCfgJSON, err := json.Marshal(newCfg) if err != nil { - return errors.Wrapf(err, "error marshalling new configuration JSON for pod %s", pod.ID()) + return fmt.Errorf("error marshalling new configuration JSON for pod %s: %w", pod.ID(), err) } db, err := s.getDBCon() @@ -1781,11 +2061,11 @@ func (s *BoltState) RewritePodConfig(pod *Pod, newCfg *PodConfig) error { podDB := podBkt.Bucket([]byte(pod.ID())) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "no pod with ID %s found in DB", pod.ID()) + return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod) } if err := podDB.Put(configKey, newCfgJSON); err != nil { - return errors.Wrapf(err, "error updating pod %s config JSON", pod.ID()) + return fmt.Errorf("error updating pod %s config JSON: %w", pod.ID(), err) } return nil @@ -1807,7 +2087,7 @@ func (s *BoltState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) er newCfgJSON, err := json.Marshal(newCfg) if err != nil { - return errors.Wrapf(err, "error marshalling new configuration JSON for volume %q", volume.Name()) + return fmt.Errorf("error marshalling new configuration JSON for volume %q: %w", volume.Name(), err) } db, err := s.getDBCon() @@ -1825,11 +2105,11 @@ func (s *BoltState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) er volDB := volBkt.Bucket([]byte(volume.Name())) if volDB == nil { volume.valid = false - return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found in DB", volume.Name()) + return fmt.Errorf("no volume with name %q found in DB: %w", volume.Name(), define.ErrNoSuchVolume) } if err := volDB.Put(configKey, newCfgJSON); err != nil { - return errors.Wrapf(err, "error updating volume %q config JSON", volume.Name()) + return fmt.Errorf("error updating volume %q config JSON: %w", volume.Name(), err) } return nil @@ -1953,7 +2233,7 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { } if strings.HasPrefix(string(checkID), idOrName) { if exists { - return errors.Wrapf(define.ErrPodExists, "more than one result for ID or name %s", idOrName) + return fmt.Errorf("more than one result for ID or name %s: %w", idOrName, define.ErrPodExists) } id = checkID exists = true @@ -1965,9 +2245,9 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { return err } else if !exists { if isCtr { - return errors.Wrapf(define.ErrNoSuchPod, "%s is a container, not a pod", idOrName) + return fmt.Errorf("%s is a container, not a pod: %w", idOrName, define.ErrNoSuchPod) } - return errors.Wrapf(define.ErrNoSuchPod, "no pod with name or ID %s found", idOrName) + return fmt.Errorf("no pod with name or ID %s found: %w", idOrName, define.ErrNoSuchPod) } // We might have found a container ID, but it's OK @@ -2043,7 +2323,7 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return false, errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return false, fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } ctrID := []byte(id) @@ -2067,13 +2347,13 @@ func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "pod %s not found in database", pod.ID()) + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) } // Get pod containers bucket podCtrs := podDB.Bucket(containersBkt) if podCtrs == nil { - return errors.Wrapf(define.ErrInternal, "pod %s missing containers bucket in DB", pod.ID()) + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) } // Don't bother with a namespace check on the container - @@ -2106,7 +2386,7 @@ func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return nil, fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } podID := []byte(pod.ID()) @@ -2129,13 +2409,13 @@ func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "pod %s not found in database", pod.ID()) + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) } // Get pod containers bucket podCtrs := podDB.Bucket(containersBkt) if podCtrs == nil { - return errors.Wrapf(define.ErrInternal, "pod %s missing containers bucket in DB", pod.ID()) + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) } // Iterate through all containers in the pod @@ -2168,7 +2448,7 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return nil, fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } podID := []byte(pod.ID()) @@ -2196,13 +2476,13 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "pod %s not found in database", pod.ID()) + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) } // Get pod containers bucket podCtrs := podDB.Bucket(containersBkt) if podCtrs == nil { - return errors.Wrapf(define.ErrInternal, "pod %s missing containers bucket in DB", pod.ID()) + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) } // Iterate through all containers in the pod @@ -2242,7 +2522,7 @@ func (s *BoltState) AddVolume(volume *Volume) error { volConfigJSON, err := json.Marshal(volume.config) if err != nil { - return errors.Wrapf(err, "error marshalling volume %s config to JSON", volume.Name()) + return fmt.Errorf("error marshalling volume %s config to JSON: %w", volume.Name(), err) } // Volume state is allowed to not exist @@ -2250,7 +2530,7 @@ func (s *BoltState) AddVolume(volume *Volume) error { if volume.state != nil { volStateJSON, err = json.Marshal(volume.state) if err != nil { - return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name()) + return fmt.Errorf("error marshalling volume %s state to JSON: %w", volume.Name(), err) } } @@ -2274,34 +2554,34 @@ func (s *BoltState) AddVolume(volume *Volume) error { // Check if we already have a volume with the given name volExists := allVolsBkt.Get(volName) if volExists != nil { - return errors.Wrapf(define.ErrVolumeExists, "name %s is in use", volume.Name()) + return fmt.Errorf("name %s is in use: %w", volume.Name(), define.ErrVolumeExists) } // We are good to add the volume // Make a bucket for it newVol, err := volBkt.CreateBucket(volName) if err != nil { - return errors.Wrapf(err, "error creating bucket for volume %s", volume.Name()) + return fmt.Errorf("error creating bucket for volume %s: %w", volume.Name(), err) } // Make a subbucket for the containers using the volume. Dependent container IDs will be addedremoved to // this bucket in addcontainer/removeContainer if _, err := newVol.CreateBucket(volDependenciesBkt); err != nil { - return errors.Wrapf(err, "error creating bucket for containers using volume %s", volume.Name()) + return fmt.Errorf("error creating bucket for containers using volume %s: %w", volume.Name(), err) } if err := newVol.Put(configKey, volConfigJSON); err != nil { - return errors.Wrapf(err, "error storing volume %s configuration in DB", volume.Name()) + return fmt.Errorf("error storing volume %s configuration in DB: %w", volume.Name(), err) } if volStateJSON != nil { if err := newVol.Put(stateKey, volStateJSON); err != nil { - return errors.Wrapf(err, "error storing volume %s state in DB", volume.Name()) + return fmt.Errorf("error storing volume %s state in DB: %w", volume.Name(), err) } } if err := allVolsBkt.Put(volName, volName); err != nil { - return errors.Wrapf(err, "error storing volume %s in all volumes bucket in DB", volume.Name()) + return fmt.Errorf("error storing volume %s in all volumes bucket in DB: %w", volume.Name(), err) } return nil @@ -2343,7 +2623,7 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { volDB := volBkt.Bucket(volName) if volDB == nil { volume.valid = false - return errors.Wrapf(define.ErrNoSuchVolume, "volume %s does not exist in DB", volume.Name()) + return fmt.Errorf("volume %s does not exist in DB: %w", volume.Name(), define.ErrNoSuchVolume) } // Check if volume is not being used by any container @@ -2370,20 +2650,20 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { return nil }) if err != nil { - return errors.Wrapf(err, "error getting list of dependencies from dependencies bucket for volumes %q", volume.Name()) + return fmt.Errorf("error getting list of dependencies from dependencies bucket for volumes %q: %w", volume.Name(), err) } if len(deps) > 0 { - return errors.Wrapf(define.ErrVolumeBeingUsed, "volume %s is being used by container(s) %s", volume.Name(), strings.Join(deps, ",")) + return fmt.Errorf("volume %s is being used by container(s) %s: %w", volume.Name(), strings.Join(deps, ","), define.ErrVolumeBeingUsed) } } // volume is ready for removal // Let's kick it out if err := allVolsBkt.Delete(volName); err != nil { - return errors.Wrapf(err, "error removing volume %s from all volumes bucket in DB", volume.Name()) + return fmt.Errorf("error removing volume %s from all volumes bucket in DB: %w", volume.Name(), err) } if err := volBkt.DeleteBucket(volName); err != nil { - return errors.Wrapf(err, "error removing volume %s from DB", volume.Name()) + return fmt.Errorf("error removing volume %s from DB: %w", volume.Name(), err) } return nil @@ -2419,7 +2699,7 @@ func (s *BoltState) UpdateVolume(volume *Volume) error { volToUpdate := volBucket.Bucket(volumeName) if volToUpdate == nil { volume.valid = false - return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %s found in database", volume.Name()) + return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) } stateBytes := volToUpdate.Get(stateKey) @@ -2430,7 +2710,7 @@ func (s *BoltState) UpdateVolume(volume *Volume) error { } if err := json.Unmarshal(stateBytes, newState); err != nil { - return errors.Wrapf(err, "error unmarshalling volume %s state", volume.Name()) + return fmt.Errorf("error unmarshalling volume %s state: %w", volume.Name(), err) } return nil @@ -2460,7 +2740,7 @@ func (s *BoltState) SaveVolume(volume *Volume) error { if volume.state != nil { stateJSON, err := json.Marshal(volume.state) if err != nil { - return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name()) + return fmt.Errorf("error marshalling volume %s state to JSON: %w", volume.Name(), err) } newStateJSON = stateJSON } @@ -2480,7 +2760,7 @@ func (s *BoltState) SaveVolume(volume *Volume) error { volToUpdate := volBucket.Bucket(volumeName) if volToUpdate == nil { volume.valid = false - return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %s found in database", volume.Name()) + return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) } return volToUpdate.Put(stateKey, newStateJSON) @@ -2517,7 +2797,7 @@ func (s *BoltState) AllVolumes() ([]*Volume, error) { // This check can be removed if performance becomes an // issue, but much less helpful errors will be produced if volExists == nil { - return errors.Wrapf(define.ErrInternal, "inconsistency in state - volume %s is in all volumes bucket but volume not found", string(id)) + return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal) } volume := new(Volume) @@ -2525,7 +2805,7 @@ func (s *BoltState) AllVolumes() ([]*Volume, error) { volume.state = new(VolumeState) if err := s.getVolumeFromDB(id, volume, volBucket); err != nil { - if errors.Cause(err) != define.ErrNSMismatch { + if !errors.Is(err, define.ErrNSMismatch) { logrus.Errorf("Retrieving volume %s from the database: %v", string(id), err) } } else { @@ -2624,7 +2904,7 @@ func (s *BoltState) LookupVolume(name string) (*Volume, error) { err = allVolsBkt.ForEach(func(checkName, checkName2 []byte) error { if strings.HasPrefix(string(checkName), name) { if foundMatch { - return errors.Wrapf(define.ErrVolumeExists, "more than one result for volume name %q", name) + return fmt.Errorf("more than one result for volume name %q: %w", name, define.ErrVolumeExists) } foundMatch = true volName = checkName @@ -2636,7 +2916,7 @@ func (s *BoltState) LookupVolume(name string) (*Volume, error) { } if !foundMatch { - return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found", name) + return fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume) } return s.getVolumeFromDB(volName, volume, volBkt) @@ -2722,12 +3002,12 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { volDB := volBucket.Bucket([]byte(volume.Name())) if volDB == nil { volume.valid = false - return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %s found in DB", volume.Name()) + return fmt.Errorf("no volume with name %s found in DB: %w", volume.Name(), define.ErrNoSuchVolume) } dependsBkt := volDB.Bucket(volDependenciesBkt) if dependsBkt == nil { - return errors.Wrapf(define.ErrInternal, "volume %s has no dependencies bucket", volume.Name()) + return fmt.Errorf("volume %s has no dependencies bucket: %w", volume.Name(), define.ErrInternal) } // Iterate through and add dependencies @@ -2767,7 +3047,7 @@ func (s *BoltState) AddPod(pod *Pod) error { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } podID := []byte(pod.ID()) @@ -2780,12 +3060,12 @@ func (s *BoltState) AddPod(pod *Pod) error { podConfigJSON, err := json.Marshal(pod.config) if err != nil { - return errors.Wrapf(err, "error marshalling pod %s config to JSON", pod.ID()) + return fmt.Errorf("error marshalling pod %s config to JSON: %w", pod.ID(), err) } podStateJSON, err := json.Marshal(pod.state) if err != nil { - return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID()) + return fmt.Errorf("error marshalling pod %s state to JSON: %w", pod.ID(), err) } db, err := s.getDBCon() @@ -2827,7 +3107,7 @@ func (s *BoltState) AddPod(pod *Pod) error { if allPodsBkt.Get(idExist) == nil { err = define.ErrCtrExists } - return errors.Wrapf(err, "ID \"%s\" is in use", pod.ID()) + return fmt.Errorf("ID \"%s\" is in use: %w", pod.ID(), err) } nameExist := namesBkt.Get(podName) if nameExist != nil { @@ -2835,47 +3115,47 @@ func (s *BoltState) AddPod(pod *Pod) error { if allPodsBkt.Get(nameExist) == nil { err = define.ErrCtrExists } - return errors.Wrapf(err, "name \"%s\" is in use", pod.Name()) + return fmt.Errorf("name \"%s\" is in use: %w", pod.Name(), err) } // We are good to add the pod // Make a bucket for it newPod, err := podBkt.CreateBucket(podID) if err != nil { - return errors.Wrapf(err, "error creating bucket for pod %s", pod.ID()) + return fmt.Errorf("error creating bucket for pod %s: %w", pod.ID(), err) } // Make a subbucket for pod containers if _, err := newPod.CreateBucket(containersBkt); err != nil { - return errors.Wrapf(err, "error creating bucket for pod %s containers", pod.ID()) + return fmt.Errorf("error creating bucket for pod %s containers: %w", pod.ID(), err) } if err := newPod.Put(configKey, podConfigJSON); err != nil { - return errors.Wrapf(err, "error storing pod %s configuration in DB", pod.ID()) + return fmt.Errorf("error storing pod %s configuration in DB: %w", pod.ID(), err) } if err := newPod.Put(stateKey, podStateJSON); err != nil { - return errors.Wrapf(err, "error storing pod %s state JSON in DB", pod.ID()) + return fmt.Errorf("error storing pod %s state JSON in DB: %w", pod.ID(), err) } if podNamespace != nil { if err := newPod.Put(namespaceKey, podNamespace); err != nil { - return errors.Wrapf(err, "error storing pod %s namespace in DB", pod.ID()) + return fmt.Errorf("error storing pod %s namespace in DB: %w", pod.ID(), err) } if err := nsBkt.Put(podID, podNamespace); err != nil { - return errors.Wrapf(err, "error storing pod %s namespace in DB", pod.ID()) + return fmt.Errorf("error storing pod %s namespace in DB: %w", pod.ID(), err) } } // 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()) + return fmt.Errorf("error storing pod %s ID in DB: %w", pod.ID(), err) } if err := namesBkt.Put(podName, podID); err != nil { - return errors.Wrapf(err, "error storing pod %s name in DB", pod.Name()) + return fmt.Errorf("error storing pod %s name in DB: %w", pod.Name(), err) } if err := allPodsBkt.Put(podID, podName); err != nil { - return errors.Wrapf(err, "error storing pod %s in all pods bucket in DB", pod.ID()) + return fmt.Errorf("error storing pod %s in all pods bucket in DB: %w", pod.ID(), err) } return nil @@ -2899,7 +3179,7 @@ func (s *BoltState) RemovePod(pod *Pod) error { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } podID := []byte(pod.ID()) @@ -2941,7 +3221,7 @@ func (s *BoltState) RemovePod(pod *Pod) error { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "pod %s does not exist in DB", pod.ID()) + return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) } // Check if pod is empty @@ -2953,26 +3233,26 @@ func (s *BoltState) RemovePod(pod *Pod) error { if podCtrsBkt != nil { cursor := podCtrsBkt.Cursor() if id, _ := cursor.First(); id != nil { - return errors.Wrapf(define.ErrCtrExists, "pod %s is not empty", pod.ID()) + return fmt.Errorf("pod %s is not empty: %w", pod.ID(), define.ErrCtrExists) } } // Pod is empty, and ready for removal // Let's kick it out if err := idsBkt.Delete(podID); err != nil { - return errors.Wrapf(err, "error removing pod %s ID from DB", pod.ID()) + return fmt.Errorf("error removing pod %s ID from DB: %w", pod.ID(), err) } if err := namesBkt.Delete(podName); err != nil { - return errors.Wrapf(err, "error removing pod %s name (%s) from DB", pod.ID(), pod.Name()) + return fmt.Errorf("error removing pod %s name (%s) from DB: %w", pod.ID(), pod.Name(), err) } if err := nsBkt.Delete(podID); err != nil { - return errors.Wrapf(err, "error removing pod %s namespace from DB", pod.ID()) + return fmt.Errorf("error removing pod %s namespace from DB: %w", pod.ID(), err) } if err := allPodsBkt.Delete(podID); err != nil { - return errors.Wrapf(err, "error removing pod %s ID from all pods bucket in DB", pod.ID()) + return fmt.Errorf("error removing pod %s ID from all pods bucket in DB: %w", pod.ID(), err) } if err := podBkt.DeleteBucket(podID); err != nil { - return errors.Wrapf(err, "error removing pod %s from DB", pod.ID()) + return fmt.Errorf("error removing pod %s from DB: %w", pod.ID(), err) } return nil @@ -2995,7 +3275,7 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } podID := []byte(pod.ID()) @@ -3036,12 +3316,12 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "pod %s does not exist in DB", pod.ID()) + return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) } podCtrsBkt := podDB.Bucket(containersBkt) if podCtrsBkt == nil { - return errors.Wrapf(define.ErrInternal, "pod %s does not have a containers bucket", pod.ID()) + return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal) } // Traverse all containers in the pod with a cursor @@ -3052,7 +3332,7 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { if ctr == nil { // This should never happen // State is inconsistent - return errors.Wrapf(define.ErrNoSuchCtr, "pod %s referenced nonexistent container %s", pod.ID(), string(id)) + return fmt.Errorf("pod %s referenced nonexistent container %s: %w", pod.ID(), string(id), define.ErrNoSuchCtr) } ctrDeps := ctr.Bucket(dependenciesBkt) // This should never be nil, but if it is, we're @@ -3061,7 +3341,7 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { err = ctrDeps.ForEach(func(depID, name []byte) error { exists := podCtrsBkt.Get(depID) if exists == nil { - return errors.Wrapf(define.ErrCtrExists, "container %s has dependency %s outside of pod %s", string(id), string(depID), pod.ID()) + return fmt.Errorf("container %s has dependency %s outside of pod %s: %w", string(id), string(depID), pod.ID(), define.ErrCtrExists) } return nil }) @@ -3073,19 +3353,19 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { // Dependencies are set, we're clear to remove if err := ctrBkt.DeleteBucket(id); err != nil { - return errors.Wrapf(define.ErrInternal, "error deleting container %s from DB", string(id)) + return fmt.Errorf("error deleting container %s from DB: %w", string(id), define.ErrInternal) } if err := idsBkt.Delete(id); err != nil { - return errors.Wrapf(err, "error deleting container %s ID in DB", string(id)) + return fmt.Errorf("error deleting container %s ID in DB: %w", string(id), err) } if err := namesBkt.Delete(name); err != nil { - return errors.Wrapf(err, "error deleting container %s name in DB", string(id)) + return fmt.Errorf("error deleting container %s name in DB: %w", string(id), err) } if err := allCtrsBkt.Delete(id); err != nil { - return errors.Wrapf(err, "error deleting container %s ID from all containers bucket in DB", string(id)) + return fmt.Errorf("error deleting container %s ID from all containers bucket in DB: %w", string(id), err) } return nil @@ -3096,10 +3376,10 @@ func (s *BoltState) RemovePodContainers(pod *Pod) error { // Delete and recreate the bucket to empty it if err := podDB.DeleteBucket(containersBkt); err != nil { - return errors.Wrapf(err, "error removing pod %s containers bucket", pod.ID()) + return fmt.Errorf("error removing pod %s containers bucket: %w", pod.ID(), err) } if _, err := podDB.CreateBucket(containersBkt); err != nil { - return errors.Wrapf(err, "error recreating pod %s containers bucket", pod.ID()) + return fmt.Errorf("error recreating pod %s containers bucket: %w", pod.ID(), err) } return nil @@ -3127,7 +3407,7 @@ func (s *BoltState) AddContainerToPod(pod *Pod, ctr *Container) error { } if ctr.config.Pod != pod.ID() { - return errors.Wrapf(define.ErrNoSuchCtr, "container %s is not part of pod %s", ctr.ID(), pod.ID()) + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) } return s.addContainer(ctr, pod) @@ -3146,19 +3426,19 @@ func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { if s.namespace != "" { if s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } if s.namespace != ctr.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "container %s in in namespace %q but we are in namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + return fmt.Errorf("container %s in in namespace %q but we are in namespace %q: %w", ctr.ID(), ctr.config.Namespace, s.namespace, define.ErrNSMismatch) } } if ctr.config.Pod == "" { - return errors.Wrapf(define.ErrNoSuchPod, "container %s is not part of a pod, use RemoveContainer instead", ctr.ID()) + return fmt.Errorf("container %s is not part of a pod, use RemoveContainer instead: %w", ctr.ID(), define.ErrNoSuchPod) } if ctr.config.Pod != pod.ID() { - return errors.Wrapf(define.ErrInvalidArg, "container %s is not part of pod %s", ctr.ID(), pod.ID()) + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg) } db, err := s.getDBCon() @@ -3184,7 +3464,7 @@ func (s *BoltState) UpdatePod(pod *Pod) error { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } newState := new(podState) @@ -3206,17 +3486,17 @@ func (s *BoltState) UpdatePod(pod *Pod) error { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "no pod with ID %s found in database", pod.ID()) + return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod) } // Get the pod state JSON podStateBytes := podDB.Get(stateKey) if podStateBytes == nil { - return errors.Wrapf(define.ErrInternal, "pod %s is missing state key in DB", pod.ID()) + return fmt.Errorf("pod %s is missing state key in DB: %w", pod.ID(), define.ErrInternal) } if err := json.Unmarshal(podStateBytes, newState); err != nil { - return errors.Wrapf(err, "error unmarshalling pod %s state JSON", pod.ID()) + return fmt.Errorf("error unmarshalling pod %s state JSON: %w", pod.ID(), err) } return nil @@ -3241,12 +3521,12 @@ func (s *BoltState) SavePod(pod *Pod) error { } if s.namespace != "" && s.namespace != pod.config.Namespace { - return errors.Wrapf(define.ErrNSMismatch, "pod %s is in namespace %q but we are in namespace %q", pod.ID(), pod.config.Namespace, s.namespace) + return fmt.Errorf("pod %s is in namespace %q but we are in namespace %q: %w", pod.ID(), pod.config.Namespace, s.namespace, define.ErrNSMismatch) } stateJSON, err := json.Marshal(pod.state) if err != nil { - return errors.Wrapf(err, "error marshalling pod %s state to JSON", pod.ID()) + return fmt.Errorf("error marshalling pod %s state to JSON: %w", pod.ID(), err) } db, err := s.getDBCon() @@ -3266,12 +3546,12 @@ func (s *BoltState) SavePod(pod *Pod) error { podDB := podBkt.Bucket(podID) if podDB == nil { pod.valid = false - return errors.Wrapf(define.ErrNoSuchPod, "no pod with ID %s found in database", pod.ID()) + return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod) } // 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 fmt.Errorf("error updating pod %s state in database: %w", pod.ID(), err) } return nil @@ -3313,7 +3593,7 @@ func (s *BoltState) AllPods() ([]*Pod, error) { // This check can be removed if performance becomes an // issue, but much less helpful errors will be produced if podExists == nil { - return errors.Wrapf(define.ErrInternal, "inconsistency in state - pod %s is in all pods bucket but pod not found", string(id)) + return fmt.Errorf("inconsistency in state - pod %s is in all pods bucket but pod not found: %w", string(id), define.ErrInternal) } pod := new(Pod) @@ -3321,7 +3601,7 @@ func (s *BoltState) AllPods() ([]*Pod, error) { pod.state = new(podState) if err := s.getPodFromDB(id, pod, podBucket); err != nil { - if errors.Cause(err) != define.ErrNSMismatch { + if !errors.Is(err, define.ErrNSMismatch) { logrus.Errorf("Retrieving pod %s from the database: %v", string(id), err) } } else { |