From f5bc2abe4c37aa859249dafd3713132e2aed1d87 Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Mon, 27 Oct 2025 15:24:51 -0400 Subject: [PATCH] Remove BoltDB state support This also includes a number of significant changes to the SQLite state made possible by removal of the legacy DB. 1. Enable database unit tests for SQLite state, with numerous tweaks to get tests passing. Most notable changes are to container removal - where we previously didn't return an error if there was no container to remove - and RemovePodContainers, which I don't think ever worked properly from my reading of the failures. 2. Removal of AddContainerToPod/RemoveContainerToPod. On SQLite, these functions are identical to AddContainer/RemoveContainer and there is no reason to retain duplicates. 3. Removal of SafeRewriteContainerConfig - it's identical to RewriteContainerConfig in SQLite, no reason to have duplicate entrypoints. As an exciting side-note, this removes Podman's requirement that containers and pods cannot share a name, which was a BoltDB restriction only. Signed-off-by: Matt Heon --- .cirrus.yml | 17 +- cmd/podman/root.go | 4 - contrib/cirrus/setup_environment.sh | 29 - go.mod | 2 +- libpod/boltdb_state.go | 3550 ------------------- libpod/boltdb_state_internal.go | 1058 ------ libpod/container.go | 10 - libpod/container_internal.go | 8 +- libpod/info.go | 2 +- libpod/networking_common.go | 65 - libpod/networking_linux_test.go | 316 -- libpod/runtime.go | 27 +- libpod/runtime_ctr.go | 34 +- libpod/runtime_test.go | 2 +- libpod/sqlite_state.go | 203 +- libpod/sqlite_state_internal.go | 101 +- libpod/state.go | 95 +- libpod/state_test.go | 283 +- pkg/domain/infra/runtime_libpod.go | 4 - pkg/specgenutil/util.go | 1 - test/e2e/common_test.go | 7 +- test/e2e/info_test.go | 62 - test/e2e/libpod_suite_remote_test.go | 4 +- test/e2e/network_connect_disconnect_test.go | 6 +- test/e2e/pod_rm_test.go | 3 - test/e2e/systemd_activate_test.go | 4 +- test/e2e/volume_rm_test.go | 4 - test/system/005-info.bats | 39 - test/system/120-load.bats | 5 - test/system/999-final.bats | 30 - test/upgrade/test-upgrade.bats | 6 +- 31 files changed, 278 insertions(+), 5703 deletions(-) delete mode 100644 libpod/boltdb_state.go delete mode 100644 libpod/boltdb_state_internal.go delete mode 100644 test/system/999-final.bats diff --git a/.cirrus.yml b/.cirrus.yml index 9c461b38f2..a99db8af13 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -57,7 +57,6 @@ env: VM_IMAGE_NAME: # One of the "Google-cloud VM Images" (above) CTR_FQIN: # One of the "Container FQIN's" (above) CI_DESIRED_RUNTIME: crun # As of 2024-05-28 there are no other supported runtimes - CI_DESIRED_DATABASE: sqlite # 'sqlite' or 'boltdb' CI_DESIRED_STORAGE: overlay # overlay, vfs, or composefs (which is actually overlay) # Curl-command prefix for downloading task artifacts, simply add the @@ -161,7 +160,6 @@ build_task: TEST_BUILD_TAGS: "" VM_IMAGE_NAME: ${PRIOR_FEDORA_CACHE_IMAGE_NAME} CTR_FQIN: ${PRIOR_FEDORA_CONTAINER_FQIN} - CI_DESIRED_DATABASE: boltdb CI_DESIRED_STORAGE: vfs - env: <<: *stdenvars @@ -684,7 +682,7 @@ compose_test_task: local_integration_test_task: &local_integration_test_task # Integration-test task name convention: # - name: &std_name_fmt "$TEST_FLAVOR $PODBIN_NAME $DISTRO_NV $PRIV_NAME $TEST_ENVIRON ${CI_DESIRED_DATABASE}" + name: &std_name_fmt "$TEST_FLAVOR $PODBIN_NAME $DISTRO_NV $PRIV_NAME $TEST_ENVIRON" alias: local_integration_test # Docs: ./contrib/cirrus/CIModes.md (Cirrus Task contexts and runtime modes) # only when: - main rules (see doc above); or @@ -737,11 +735,6 @@ container_integration_test_task: DISTRO_NV: ${FEDORA_NAME} VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} CTR_FQIN: ${FEDORA_CONTAINER_FQIN} - - env: - DISTRO_NV: ${PRIOR_FEDORA_NAME} - VM_IMAGE_NAME: ${PRIOR_FEDORA_CACHE_IMAGE_NAME} - CTR_FQIN: ${PRIOR_FEDORA_CONTAINER_FQIN} - CI_DESIRED_DATABASE: boltdb gce_instance: *fastvm env: TEST_FLAVOR: int @@ -1095,12 +1088,6 @@ upgrade_test_task: (changesInclude('**/*.go', '**/*.c', '**/*.h') && !changesIncludeOnly('test/**', 'pkg/machine/e2e/**')) depends_on: *build matrix: - - env: - # 2024-02: as long as possible/reasonable, try to keep - # one version < 4.8 so we can test boltdb. v4.3.1 is - # the lowest we can go right now, builds before that - # have netavark <1.4 which hangs on f39 kernel (#21863). - PODMAN_UPGRADE_FROM: v4.3.1 - env: PODMAN_UPGRADE_FROM: v4.8.0 gce_instance: *standardvm @@ -1109,8 +1096,6 @@ upgrade_test_task: TEST_BUILD_TAGS: "" DISTRO_NV: ${FEDORA_NAME} VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} - # Never force a DB, let the old version decide its default - CI_DESIRED_DATABASE: clone_script: *get_gosrc setup_script: *setup main_script: *main diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 303d163043..bc4fccac65 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -569,9 +569,6 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) { lFlags.StringArray(moduleFlagName, nil, "Load the containers.conf(5) module") _ = cmd.RegisterFlagCompletionFunc(moduleFlagName, common.AutocompleteContainersConfModules) - // A *hidden* flag to change the database backend. - pFlags.StringVar(&podmanConfig.ContainersConf.Engine.DBBackend, "db-backend", podmanConfig.ContainersConfDefaultsRO.Engine.DBBackend, "Database backend to use") - cgroupManagerFlagName := "cgroup-manager" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.CgroupManager, cgroupManagerFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") _ = cmd.RegisterFlagCompletionFunc(cgroupManagerFlagName, common.AutocompleteCgroupManager) @@ -659,7 +656,6 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) { // Hide these flags for both ABI and Tunneling for _, f := range []string{ "cpu-profile", - "db-backend", "default-mounts-file", "max-workers", "memory-profile", diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 2b19eeaccc..9d67774da8 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -99,14 +99,6 @@ mkdir -p /etc/containers/containers.conf.d [[ "$CG_FS_TYPE" == "cgroup2fs" ]] || \ die "Only cgroups v2 CI VMs are supported, not: '$CG_FS_TYPE'" -# For testing boltdb without having to use --db-backend. -# As of #20318 (2023-10-10) sqlite is the default, so do not create -# a containers.conf file in that condition. -# shellcheck disable=SC2154 -if [[ "${CI_DESIRED_DATABASE:-sqlite}" != "sqlite" ]]; then - printf "[engine]\ndatabase_backend=\"$CI_DESIRED_DATABASE\"\n" > /etc/containers/containers.conf.d/92-db.conf -fi - if ((CONTAINER==0)); then # Not yet running inside a container showrun echo "conditional setup for CONTAINER == 0" # Discovered reemergence of BFQ scheduler bug in kernel 5.8.12-200 @@ -151,27 +143,6 @@ case "$OS_RELEASE_ID" in *) die_unknown OS_RELEASE_ID esac -# Database: force SQLite or BoltDB as requested in .cirrus.yml. -# If unset, will default to SQLite. -# shellcheck disable=SC2154 -showrun echo "about to set up for CI_DESIRED_DATABASE [=$CI_DESIRED_DATABASE]" -case "$CI_DESIRED_DATABASE" in - sqlite) - warn "Forcing PODMAN_DB=sqlite" - echo "PODMAN_DB=sqlite" >> /etc/ci_environment - ;; - boltdb) - warn "Forcing PODMAN_DB=boltdb" - echo "PODMAN_DB=boltdb" >> /etc/ci_environment - ;; - "") - warn "Using default Podman database" - ;; - *) - die_unknown CI_DESIRED_DATABASE - ;; -esac - # Force the requested storage driver for both system and e2e tests. # This is (sigh) different because e2e tests have their own special way # of ignoring system defaults. diff --git a/go.mod b/go.mod index 84b7b8deb7..dcfabfbf04 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,6 @@ require ( github.com/stretchr/testify v1.11.1 github.com/vbauerster/mpb/v8 v8.10.2 github.com/vishvananda/netlink v1.3.1 - go.etcd.io/bbolt v1.4.3 go.podman.io/common v0.66.0 go.podman.io/image/v5 v5.38.0 go.podman.io/storage v1.61.0 @@ -173,6 +172,7 @@ require ( github.com/vbatts/tar-split v0.12.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go deleted file mode 100644 index 20b2b4471c..0000000000 --- a/libpod/boltdb_state.go +++ /dev/null @@ -1,3550 +0,0 @@ -//go:build !remote - -package libpod - -import ( - "bytes" - "errors" - "fmt" - "io/fs" - "net" - "os" - "strconv" - "strings" - "sync" - "time" - - "github.com/containers/podman/v6/libpod/define" - "github.com/sirupsen/logrus" - bolt "go.etcd.io/bbolt" - "go.podman.io/common/libnetwork/types" - "go.podman.io/storage/pkg/fileutils" -) - -// BoltState is a state implementation backed by a Bolt DB -type BoltState struct { - valid bool - dbPath string - dbLock sync.Mutex - runtime *Runtime -} - -// A brief description of the format of the BoltDB state: -// At the top level, the following buckets are created: -// - idRegistryBkt: Maps ID to Name for containers and pods. -// Used to ensure container and pod IDs are globally unique. -// - nameRegistryBkt: Maps Name to ID for containers and pods. -// Used to ensure container and pod names are globally unique. -// - ctrBkt: Contains a sub-bucket for each container in the state. -// Each sub-bucket has config and state keys holding the container's JSON -// encoded configuration and state (respectively), an optional netNS key -// containing the path to the container's network namespace, a dependencies -// bucket containing the container's dependencies, and an optional pod key -// containing the ID of the pod the container is joined to. -// After updates to include exec sessions, may also include an exec bucket -// with the IDs of exec sessions currently in use by the container. -// - allCtrsBkt: Map of ID to name containing only containers. Used for -// container lookup operations. -// - podBkt: Contains a sub-bucket for each pod in the state. -// Each sub-bucket has config and state keys holding the pod's JSON encoded -// configuration and state, plus a containers sub bucket holding the IDs of -// containers in the pod. -// - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup -// operations. -// - execBkt: Map of exec session ID to container ID - used for resolving -// exec session IDs to the containers that hold the exec session. -// - networksBkt: Contains all network names as key with their options json -// encoded as value. -// - aliasesBkt - Deprecated, use the networksBkt. Used to contain a bucket -// for each CNI network which contain a map of network alias (an extra name -// for containers in DNS) to the ID of the container holding the alias. -// Aliases must be unique per-network, and cannot conflict with names -// registered in nameRegistryBkt. -// - runtimeConfigBkt: Contains configuration of the libpod instance that -// 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)marshalling, 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) { - logrus.Info("Using boltdb as database backend") - state := new(BoltState) - state.dbPath = path - state.runtime = runtime - - logrus.Debugf("Initializing boltdb state at %s", path) - - ciDesiredDB := os.Getenv("CI_DESIRED_DATABASE") - - // BoltDB is deprecated and, as of Podman 5.0, we no longer allow the - // creation of new Bolt states. - // If the DB does not already exist, error out. - // To continue testing in CI, allow creation iff an undocumented env - // var is set. - if ciDesiredDB != "boltdb" { - if err := fileutils.Exists(path); err != nil && errors.Is(err, fs.ErrNotExist) { - return nil, fmt.Errorf("the BoltDB backend has been deprecated, no new BoltDB databases can be created: %w", define.ErrInvalidArg) - } - } else { - logrus.Debugf("Allowing deprecated database backend due to CI_DESIRED_DATABASE.") - } - - // TODO: Up this to ERROR level in 5.8 - if os.Getenv("SUPPRESS_BOLTDB_WARNING") == "" && ciDesiredDB != "boltdb" { - logrus.Warnf("The deprecated BoltDB database driver is in use. This driver will be removed in the upcoming Podman 6.0 release in mid 2026. It is advised that you migrate to SQLite to avoid issues when this occurs. Set SUPPRESS_BOLTDB_WARNING environment variable to remove this message.") - } - - db, err := bolt.Open(path, 0o600, nil) - if err != nil { - return nil, fmt.Errorf("opening database %s: %w", path, err) - } - // Everywhere else, we use s.deferredCloseDBCon(db) to ensure the state's DB - // mutex is also unlocked. - // However, here, the mutex has not been locked, since we just created - // the DB connection, and it hasn't left this function yet - no risk of - // concurrent access. - // As such, just a db.Close() is fine here. - defer db.Close() - - createBuckets := [][]byte{ - idRegistryBkt, - nameRegistryBkt, - ctrBkt, - allCtrsBkt, - podBkt, - allPodsBkt, - volBkt, - allVolsBkt, - execBkt, - runtimeConfigBkt, - exitCodeBkt, - exitCodeTimeStampBkt, - volCtrsBkt, - } - - // Does the DB need an update? - needsUpdate := false - err = db.View(func(tx *bolt.Tx) error { - for _, bkt := range createBuckets { - if test := tx.Bucket(bkt); test == nil { - needsUpdate = true - break - } - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("checking DB schema: %w", err) - } - - if !needsUpdate { - state.valid = true - return state, nil - } - - // Ensure schema is properly created in DB - err = db.Update(func(tx *bolt.Tx) error { - for _, bkt := range createBuckets { - if _, err := tx.CreateBucketIfNotExists(bkt); err != nil { - return fmt.Errorf("creating bucket %s: %w", string(bkt), err) - } - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("creating buckets for DB: %w", 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 define.ErrDBClosed - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - idBucket, err := getIDBucket(tx) - if err != nil { - return err - } - - namesBucket, err := getNamesBucket(tx) - if err != nil { - return err - } - - ctrsBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - podsBucket, err := getPodBucket(tx) - if err != nil { - return err - } - - allVolsBucket, err := getAllVolsBucket(tx) - if err != nil { - return err - } - - volBucket, err := getVolBucket(tx) - if err != nil { - return err - } - - execBucket, err := getExecBucket(tx) - if err != nil { - 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("reading exit codes bucket: %w", err) - } - for _, id := range toRemoveExitCodes { - if err := exitCodeBucket.Delete([]byte(id)); err != nil { - return fmt.Errorf("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, _ []byte) error { - ctrBkt := ctrsBucket.Bucket(id) - if ctrBkt == nil { - // It's a pod - podBkt := podsBucket.Bucket(id) - if podBkt == nil { - // This is neither a pod nor a container - // 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 fmt.Errorf("pod %s missing state key: %w", string(id), define.ErrInternal) - } - - state := new(podState) - - if err := json.Unmarshal(stateBytes, state); err != nil { - return fmt.Errorf("unmarshalling state for pod %s: %w", string(id), err) - } - - // Refresh the state - resetPodState(state) - - newStateBytes, err := json.Marshal(state) - if err != nil { - return fmt.Errorf("marshalling modified state for pod %s: %w", string(id), err) - } - - if err := podBkt.Put(stateKey, newStateBytes); err != nil { - return fmt.Errorf("updating state for pod %s in DB: %w", string(id), err) - } - - // It's not a container, nothing to do - return nil - } - - // First, delete the network namespace - if err := ctrBkt.Delete(netNSKey); err != nil { - return fmt.Errorf("removing network namespace for container %s: %w", string(id), err) - } - - stateBytes := ctrBkt.Get(stateKey) - if stateBytes == nil { - // Badly formatted container bucket - 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 fmt.Errorf("unmarshalling state for container %s: %w", string(id), err) - } - - resetContainerState(state) - - newStateBytes, err := json.Marshal(state) - if err != nil { - return fmt.Errorf("marshalling modified state for container %s: %w", string(id), err) - } - - if err := ctrBkt.Put(stateKey, newStateBytes); err != nil { - return fmt.Errorf("updating state for container %s in DB: %w", string(id), err) - } - - // Delete all exec sessions, if there are any - ctrExecBkt := ctrBkt.Bucket(execBkt) - if ctrExecBkt != nil { - // Can't delete in a ForEach, so build a list of - // what to remove then remove. - toRemove := []string{} - err = ctrExecBkt.ForEach(func(id, _ []byte) error { - toRemove = append(toRemove, string(id)) - return nil - }) - if err != nil { - return err - } - for _, execID := range toRemove { - if err := ctrExecBkt.Delete([]byte(execID)); err != nil { - return fmt.Errorf("removing exec session %s from container %s: %w", execID, string(id), err) - } - } - } - - return nil - }) - if err != nil { - 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("removing dangling name %s (ID %s) from database: %w", string(name), id, err) - } - } - } - if err := idBucket.Delete([]byte(id)); err != nil { - return fmt.Errorf("removing dangling ID %s from database: %w", id, err) - } - } - - // Now refresh volumes - err = allVolsBucket.ForEach(func(id, _ []byte) error { - dbVol := volBucket.Bucket(id) - if dbVol == nil { - 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 - volStateBytes := dbVol.Get(stateKey) - if volStateBytes == nil { - // If the volume doesn't have a state, nothing to do - return nil - } - - oldState := new(VolumeState) - - if err := json.Unmarshal(volStateBytes, oldState); err != nil { - return fmt.Errorf("unmarshalling state for volume %s: %w", string(id), err) - } - - resetVolumeState(oldState) - - newState, err := json.Marshal(oldState) - if err != nil { - return fmt.Errorf("marshalling state for volume %s: %w", string(id), err) - } - - if err := dbVol.Put(stateKey, newState); err != nil { - return fmt.Errorf("storing new state for volume %s: %w", string(id), err) - } - - return nil - }) - if err != nil { - return err - } - - // Now refresh exec sessions - // We want to remove them all, but for-each can't modify buckets - // So we have to make a list of what to operate on, then do the - // work. - toRemoveExec := []string{} - err = execBucket.ForEach(func(id, _ []byte) error { - toRemoveExec = append(toRemoveExec, string(id)) - return nil - }) - if err != nil { - return err - } - - for _, execSession := range toRemoveExec { - if err := execBucket.Delete([]byte(execSession)); err != nil { - return fmt.Errorf("deleting exec session %s registry from database: %w", execSession, err) - } - } - - return nil - }) - return err -} - -// GetDBConfig retrieves runtime configuration fields that were created when -// the database was first initialized -func (s *BoltState) GetDBConfig() (*DBConfig, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - cfg := new(DBConfig) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - configBucket, err := getRuntimeConfigBucket(tx) - if err != nil { - return err - } - - // Some of these may be nil - // When we convert to string, Go will coerce them to "" - // That's probably fine - we could raise an error if the key is - // missing, but just not including it is also OK. - libpodRoot := configBucket.Get(staticDirKey) - libpodTmp := configBucket.Get(tmpDirKey) - storageRoot := configBucket.Get(graphRootKey) - storageTmp := configBucket.Get(runRootKey) - graphDriver := configBucket.Get(graphDriverKey) - volumePath := configBucket.Get(volPathKey) - - cfg.LibpodRoot = string(libpodRoot) - cfg.LibpodTmp = string(libpodTmp) - cfg.StorageRoot = string(storageRoot) - cfg.StorageTmp = string(storageTmp) - cfg.GraphDriver = string(graphDriver) - cfg.VolumePath = string(volumePath) - - return nil - }) - if err != nil { - return nil, err - } - - return cfg, nil -} - -// ValidateDBConfig validates paths in the given runtime against the database -func (s *BoltState) ValidateDBConfig(runtime *Runtime) error { - if !s.valid { - return define.ErrDBClosed - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - // Check runtime configuration - if err := checkRuntimeConfig(db, runtime); err != nil { - return err - } - - return nil -} - -// GetContainerName returns the name associated with a given ID. -// Returns ErrNoSuchCtr if the ID does not exist. -func (s *BoltState) GetContainerName(id string) (string, error) { - if id == "" { - return "", define.ErrEmptyID - } - - if !s.valid { - return "", define.ErrDBClosed - } - - idBytes := []byte(id) - - db, err := s.getDBCon() - if err != nil { - return "", err - } - defer s.deferredCloseDBCon(db) - - name := "" - - err = db.View(func(tx *bolt.Tx) error { - idBkt, err := getIDBucket(tx) - if err != nil { - return err - } - - ctrsBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - nameBytes := idBkt.Get(idBytes) - if nameBytes == nil { - return define.ErrNoSuchCtr - } - - ctrExists := ctrsBkt.Bucket(idBytes) - if ctrExists == nil { - return define.ErrNoSuchCtr - } - - name = string(nameBytes) - return nil - }) - if err != nil { - return "", err - } - - return name, nil -} - -// GetPodName returns the name associated with a given ID. -// Returns ErrNoSuchPod if the ID does not exist. -func (s *BoltState) GetPodName(id string) (string, error) { - if id == "" { - return "", define.ErrEmptyID - } - - if !s.valid { - return "", define.ErrDBClosed - } - - idBytes := []byte(id) - - db, err := s.getDBCon() - if err != nil { - return "", err - } - defer s.deferredCloseDBCon(db) - - name := "" - - err = db.View(func(tx *bolt.Tx) error { - idBkt, err := getIDBucket(tx) - if err != nil { - return err - } - - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - nameBytes := idBkt.Get(idBytes) - if nameBytes == nil { - return define.ErrNoSuchPod - } - - podExists := podBkt.Bucket(idBytes) - if podExists == nil { - return define.ErrNoSuchPod - } - - name = string(nameBytes) - return nil - }) - if err != nil { - return "", err - } - - return name, nil -} - -// Container retrieves a single container from the state by its full ID -func (s *BoltState) Container(id string) (*Container, error) { - if id == "" { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.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 s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - return s.getContainerFromDB(ctrID, ctr, ctrBucket, false) - }) - if err != nil { - return nil, err - } - - return ctr, nil -} - -// LookupContainerID retrieves a container ID from the state by full or unique -// partial ID or name -func (s *BoltState) LookupContainerID(idOrName string) (string, error) { - if idOrName == "" { - return "", define.ErrEmptyID - } - - if !s.valid { - return "", define.ErrDBClosed - } - - db, err := s.getDBCon() - if err != nil { - return "", err - } - defer s.deferredCloseDBCon(db) - - var id []byte - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - namesBucket, err := getNamesBucket(tx) - if err != nil { - return err - } - - fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket) - id = fullID - return err - }) - - if err != nil { - return "", err - } - - retID := string(id) - return retID, 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, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - ctr := new(Container) - ctr.config = new(ContainerConfig) - ctr.state = new(ContainerState) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - namesBucket, err := getNamesBucket(tx) - if err != nil { - return err - } - - id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket) - if err != nil { - return err - } - - return s.getContainerFromDB(id, ctr, ctrBucket, false) - }) - 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, define.ErrEmptyID - } - - if !s.valid { - return false, define.ErrDBClosed - } - - ctrID := []byte(id) - - db, err := s.getDBCon() - if err != nil { - return false, err - } - defer s.deferredCloseDBCon(db) - - exists := false - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - ctrDB := ctrBucket.Bucket(ctrID) - if ctrDB != 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 define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if ctr.config.Pod != "" { - return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg) - } - - return s.addContainer(ctr, nil) -} - -// RemoveContainer removes a container from the state -// Only removes containers not in pods - for containers that are a member of a -// pod, use RemoveContainerFromPod -func (s *BoltState) RemoveContainer(ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if ctr.config.Pod != "" { - return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - return s.removeContainer(ctr, nil, tx) - }) - return err -} - -// UpdateContainer updates a container's state from the database -func (s *BoltState) UpdateContainer(ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - return db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - return s.getContainerStateDB(ctrID, ctr, ctrBucket) - }) -} - -// SaveContainer saves a container's current state in the database -func (s *BoltState) SaveContainer(ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - stateJSON, err := json.Marshal(ctr.state) - if err != nil { - return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err) - } - netNSPath := ctr.state.NetNS - - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - ctrToSave := ctrBucket.Bucket(ctrID) - if ctrToSave == nil { - ctr.valid = false - 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 fmt.Errorf("updating container %s state in DB: %w", ctr.ID(), err) - } - - if netNSPath == "" { - // Delete the existing network namespace - if err := ctrToSave.Delete(netNSKey); err != nil { - return fmt.Errorf("removing network namespace path for container %s in DB: %w", ctr.ID(), err) - } - } - - 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, define.ErrDBClosed - } - - if !ctr.valid { - return nil, define.ErrCtrRemoved - } - - depCtrs := []string{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - ctrDB := ctrBucket.Bucket([]byte(ctr.ID())) - if ctrDB == nil { - ctr.valid = false - return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - dependsBkt := ctrDB.Bucket(dependenciesBkt) - if dependsBkt == nil { - return fmt.Errorf("container %s has no dependencies bucket: %w", ctr.ID(), define.ErrInternal) - } - - // Iterate through and add dependencies - err = dependsBkt.ForEach(func(id, _ []byte) error { - depCtrs = append(depCtrs, string(id)) - - return nil - }) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - return depCtrs, nil -} - -// AllContainers retrieves all the containers in the database -// If `loadState` is set, the containers' state will be loaded as well. -func (s *BoltState) AllContainers(loadState bool) ([]*Container, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - ctrs := []*Container{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - allCtrsBucket, err := getAllCtrsBucket(tx) - if err != nil { - return err - } - - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - return allCtrsBucket.ForEach(func(id, _ []byte) error { - // If performance becomes an issue, this check can be - // removed. But the error messages that come back will - // be much less helpful. - ctrExists := ctrBucket.Bucket(id) - if ctrExists == nil { - return fmt.Errorf("state is inconsistent - container ID %s in all containers, but container not found: %w", string(id), define.ErrInternal) - } - - ctr := new(Container) - ctr.config = new(ContainerConfig) - ctr.state = new(ContainerState) - - if err := s.getContainerFromDB(id, ctr, ctrBucket, loadState); err != nil { - logrus.Errorf("Error retrieving container from database: %v", err) - } else { - ctrs = append(ctrs, ctr) - } - - return nil - }) - }) - if err != nil { - return nil, err - } - - return ctrs, nil -} - -// GetNetworks returns the networks this container is a part of. -func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !ctr.valid { - return nil, define.ErrCtrRemoved - } - - // if the network mode is not bridge return no networks - if !ctr.config.NetMode.IsBridge() { - return nil, nil - } - - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - networks := make(map[string]types.PerNetworkOptions) - - var convertDB bool - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - ctrNetworkBkt := dbCtr.Bucket(networksBkt) - if ctrNetworkBkt == nil { - // convert if needed - convertDB = true - return nil - } - - return ctrNetworkBkt.ForEach(func(network, v []byte) error { - opts := types.PerNetworkOptions{} - if err := json.Unmarshal(v, &opts); err != nil { - // special case for backwards compat - // earlier version used the container id as value so we set a - // special error to indicate the we have to migrate the db - if !bytes.Equal(v, ctrID) { - return err - } - convertDB = true - } - networks[string(network)] = opts - return nil - }) - }) - if err != nil { - return nil, err - } - if convertDB { - err = db.Update(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - var networkList []string - - ctrNetworkBkt := dbCtr.Bucket(networksBkt) - if ctrNetworkBkt == nil { - ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt) - if err != nil { - return fmt.Errorf("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 - // if there are no networks we have to add the default - if len(networkList) == 0 { - networkList = []string{ctr.runtime.config.Network.DefaultNetwork} - } - } else { - err = ctrNetworkBkt.ForEach(func(network, _ []byte) error { - networkList = append(networkList, string(network)) - return nil - }) - if err != nil { - return err - } - } - - // the container has no networks in the db lookup config and write to the db - for i, network := range networkList { - var intName string - if ctr.state.NetInterfaceDescriptions != nil { - eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network) - if !exists { - return fmt.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) - } - intName = eth - } else { - intName = fmt.Sprintf("eth%d", i) - } - getAliases := func(network string) []string { - var aliases []string - ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) - if ctrAliasesBkt == nil { - return nil - } - netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) - if netAliasesBkt == nil { - // No aliases for this specific network. - return nil - } - - // let's ignore the error here there is nothing we can do - _ = netAliasesBkt.ForEach(func(alias, _ []byte) error { - aliases = append(aliases, string(alias)) - return nil - }) - // also add the short container id as alias - return aliases - } - - netOpts := &types.PerNetworkOptions{ - InterfaceName: intName, - // we have to add the short id as alias for docker compat - Aliases: append(getAliases(network), ctr.config.ID[:12]), - } - // only set the static ip/mac on the first network - if i == 0 { - if ctr.config.StaticIP != nil { - netOpts.StaticIPs = []net.IP{ctr.config.StaticIP} - } - netOpts.StaticMAC = ctr.config.StaticMAC - } - - optsBytes, err := json.Marshal(netOpts) - if err != nil { - return err - } - // insert into network map because we need to return this - networks[network] = *netOpts - - err = ctrNetworkBkt.Put([]byte(network), optsBytes) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return nil, err - } - } - - return networks, nil -} - -// NetworkConnect adds the given container to the given network. If aliases are -// specified, those will be added to the given network. -func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error { - return s.networkModify(ctr, network, opts, true) -} - -// NetworkModify will allow you to set new options on an existing connected network -func (s *BoltState) NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error { - return s.networkModify(ctr, network, opts, false) -} - -// networkModify allows you to modify or add a new network, to add a new network use the new bool -func (s *BoltState) networkModify(ctr *Container, network string, opts types.PerNetworkOptions, new bool) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if network == "" { - return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) - } - - optBytes, err := json.Marshal(opts) - if err != nil { - return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err) - } - - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - return db.Update(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - ctrNetworksBkt := dbCtr.Bucket(networksBkt) - if ctrNetworksBkt == nil { - return fmt.Errorf("container %s does not have a network bucket: %w", ctr.ID(), define.ErrNoSuchNetwork) - } - netConnected := ctrNetworksBkt.Get([]byte(network)) - - if new && netConnected != nil { - return fmt.Errorf("container %s is already connected to network %q: %w", ctr.ID(), network, define.ErrNetworkConnected) - } else if !new && netConnected == nil { - return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork) - } - - // Modify/Add the network - if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil { - return fmt.Errorf("adding container %s to network %s in DB: %w", ctr.ID(), network, err) - } - - return nil - }) -} - -// NetworkDisconnect disconnects the container from the given network, also -// removing any aliases in the network. -func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if network == "" { - return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) - } - - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - return db.Update(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - 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 fmt.Errorf("container %s is not connected to any networks, so cannot disconnect: %w", ctr.ID(), define.ErrNoSuchNetwork) - } - netConnected := ctrNetworksBkt.Get([]byte(network)) - if netConnected == nil { - return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork) - } - - if err := ctrNetworksBkt.Delete([]byte(network)); err != nil { - return fmt.Errorf("removing container %s from network %s: %w", ctr.ID(), network, err) - } - - if ctrAliasesBkt != nil { - bktExists := ctrAliasesBkt.Bucket([]byte(network)) - if bktExists == nil { - return nil - } - - if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil { - return fmt.Errorf("removing container %s network aliases for network %s: %w", ctr.ID(), network, err) - } - } - - return nil - }) -} - -// GetContainerConfig returns a container config from the database by full ID -func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { - if len(id) == 0 { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - config := new(ContainerConfig) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - return s.getContainerConfigFromDB([]byte(id), config, ctrBucket) - }) - if err != nil { - return nil, err - } - - 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("marshalling 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 - }) -} - -// PruneContainerExitCodes removes exit codes older than 5 minutes unless the associated -// container still exists. -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 - } - - ctrsBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error { - if ctrsBucket.Bucket(rawID) != nil { - // If the container still exists, don't prune - // its exit code since we may still need it. - return nil - } - 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 { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - ctrID := []byte(ctr.ID()) - sessionID := []byte(session.ID()) - - err = db.Update(func(tx *bolt.Tx) error { - execBucket, err := getExecBucket(tx) - if err != nil { - return err - } - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - 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 fmt.Errorf("creating exec sessions bucket for container %s: %w", ctr.ID(), err) - } - - execExists := execBucket.Get(sessionID) - if execExists != nil { - 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 fmt.Errorf("adding exec session %s to DB: %w", session.ID(), err) - } - - if err := ctrExecSessionBucket.Put(sessionID, ctrID); err != nil { - return fmt.Errorf("adding exec session %s to container %s in DB: %w", session.ID(), ctr.ID(), err) - } - - return nil - }) - return err -} - -// GetExecSession returns the ID of the container an exec session is associated -// with. -func (s *BoltState) GetExecSession(id string) (string, error) { - if !s.valid { - return "", define.ErrDBClosed - } - - if id == "" { - return "", define.ErrEmptyID - } - - db, err := s.getDBCon() - if err != nil { - return "", err - } - defer s.deferredCloseDBCon(db) - - ctrID := "" - err = db.View(func(tx *bolt.Tx) error { - execBucket, err := getExecBucket(tx) - if err != nil { - return err - } - - ctr := execBucket.Get([]byte(id)) - if ctr == nil { - return fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession) - } - ctrID = string(ctr) - return nil - }) - return ctrID, err -} - -// RemoveExecSession removes references to the given exec session in the -// database. -func (s *BoltState) RemoveExecSession(session *ExecSession) error { - if !s.valid { - return define.ErrDBClosed - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - sessionID := []byte(session.ID()) - containerID := []byte(session.ContainerID()) - err = db.Update(func(tx *bolt.Tx) error { - execBucket, err := getExecBucket(tx) - if err != nil { - return err - } - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - sessionExists := execBucket.Get(sessionID) - if sessionExists == nil { - return define.ErrNoSuchExecSession - } - // Check that container ID matches - if string(sessionExists) != session.ContainerID() { - 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 fmt.Errorf("removing exec session %s from database: %w", session.ID(), err) - } - - dbCtr := ctrBucket.Bucket(containerID) - if dbCtr == nil { - // State is inconsistent. We refer to a container that - // is no longer in the state. - // Return without error, to attempt to recover. - return nil - } - - ctrExecBucket := dbCtr.Bucket(execBkt) - if ctrExecBucket == nil { - // Again, state is inconsistent. We should have an exec - // bucket, and it should have this session. - // Again, nothing we can do, so proceed and try to - // recover. - return nil - } - - ctrSessionExists := ctrExecBucket.Get(sessionID) - if ctrSessionExists != nil { - if err := ctrExecBucket.Delete(sessionID); err != nil { - return fmt.Errorf("removing exec session %s from container %s in database: %w", session.ID(), session.ContainerID(), err) - } - } - - return nil - }) - return err -} - -// GetContainerExecSessions retrieves the IDs of all exec sessions running in a -// container that the database is aware of (IE, were added via AddExecSession). -func (s *BoltState) GetContainerExecSessions(ctr *Container) ([]string, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !ctr.valid { - return nil, define.ErrCtrRemoved - } - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - ctrID := []byte(ctr.ID()) - sessions := []string{} - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return define.ErrNoSuchCtr - } - - ctrExecSessions := dbCtr.Bucket(execBkt) - if ctrExecSessions == nil { - return nil - } - - return ctrExecSessions.ForEach(func(id, _ []byte) error { - sessions = append(sessions, string(id)) - return nil - }) - }) - if err != nil { - return nil, err - } - - return sessions, nil -} - -// RemoveContainerExecSessions removes all exec sessions attached to a given -// container. -func (s *BoltState) RemoveContainerExecSessions(ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - ctrID := []byte(ctr.ID()) - sessions := []string{} - - err = db.Update(func(tx *bolt.Tx) error { - execBucket, err := getExecBucket(tx) - if err != nil { - return err - } - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return define.ErrNoSuchCtr - } - - ctrExecSessions := dbCtr.Bucket(execBkt) - if ctrExecSessions == nil { - return nil - } - - err = ctrExecSessions.ForEach(func(id, _ []byte) error { - sessions = append(sessions, string(id)) - return nil - }) - if err != nil { - return err - } - - for _, session := range sessions { - if err := ctrExecSessions.Delete([]byte(session)); err != nil { - return fmt.Errorf("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 - // has become inconsistent, we should try and proceed - // so we can recover. - sessionExists := execBucket.Get([]byte(session)) - if sessionExists == nil { - continue - } - if string(sessionExists) != ctr.ID() { - 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 fmt.Errorf("removing container %s exec session %s from exec sessions: %w", ctr.ID(), session, err) - } - } - - return nil - }) - return err -} - -// RewriteContainerConfig rewrites a container's configuration. -// WARNING: This function is DANGEROUS. Do not use without reading the full -// comment on this function in state.go. -func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - newCfgJSON, err := json.Marshal(newCfg) - if err != nil { - return fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - ctrBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) - if ctrDB == nil { - ctr.valid = false - 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 fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err) - } - - return nil - }) - return err -} - -// SafeRewriteContainerConfig rewrites a container's configuration in a more -// limited fashion than RewriteContainerConfig. It is marked as safe to use -// under most circumstances, unlike RewriteContainerConfig. -// DO NOT USE TO: Change container dependencies, change pod membership, change -// locks, change container ID. -func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if newName != "" && newCfg.Name != newName { - 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 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 fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - if newName != "" { - idBkt, err := getIDBucket(tx) - if err != nil { - return err - } - namesBkt, err := getNamesBucket(tx) - if err != nil { - return err - } - allCtrsBkt, err := getAllCtrsBucket(tx) - if err != nil { - return err - } - - needsRename := true - if exists := namesBkt.Get([]byte(newName)); exists != nil { - if string(exists) == ctr.ID() { - // Name already associated with the ID - // of this container. No need for a - // rename. - needsRename = false - } else { - return fmt.Errorf("name %s already in use, cannot rename container %s: %w", newName, ctr.ID(), define.ErrCtrExists) - } - } - - if needsRename { - // We do have to remove the old name. The other - // buckets are ID-indexed so we just need to - // overwrite the values there. - if err := namesBkt.Delete([]byte(oldName)); err != nil { - return fmt.Errorf("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 fmt.Errorf("renaming container %s in ID bucket in DB: %w", ctr.ID(), err) - } - if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil { - return fmt.Errorf("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 fmt.Errorf("renaming container %s in all containers bucket in DB: %w", ctr.ID(), err) - } - if ctr.config.Pod != "" { - podsBkt, err := getPodBucket(tx) - if err != nil { - return err - } - podBkt := podsBkt.Bucket([]byte(ctr.config.Pod)) - if podBkt == nil { - return fmt.Errorf("bucket for pod %s does not exist: %w", ctr.config.Pod, define.ErrInternal) - } - podCtrBkt := podBkt.Bucket(containersBkt) - if podCtrBkt == nil { - 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 fmt.Errorf("renaming container %s in pod %s members bucket: %w", ctr.ID(), ctr.config.Pod, err) - } - } - } - } - - ctrBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) - if ctrDB == nil { - ctr.valid = false - 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 fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err) - } - - return nil - }) - return err -} - -// RewritePodConfig rewrites a pod's configuration. -// WARNING: This function is DANGEROUS. Do not use without reading the full -// comment on this function in state.go. -func (s *BoltState) RewritePodConfig(pod *Pod, newCfg *PodConfig) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - newCfgJSON, err := json.Marshal(newCfg) - if err != nil { - return fmt.Errorf("marshalling new configuration JSON for pod %s: %w", pod.ID(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - podDB := podBkt.Bucket([]byte(pod.ID())) - if podDB == nil { - pod.valid = false - 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 fmt.Errorf("updating pod %s config JSON: %w", pod.ID(), err) - } - - return nil - }) - return err -} - -// RewriteVolumeConfig rewrites a volume's configuration. -// WARNING: This function is DANGEROUS. Do not use without reading the full -// comment on this function in state.go. -func (s *BoltState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) error { - if !s.valid { - return define.ErrDBClosed - } - - if !volume.valid { - return define.ErrVolumeRemoved - } - - newCfgJSON, err := json.Marshal(newCfg) - if err != nil { - return fmt.Errorf("marshalling new configuration JSON for volume %q: %w", volume.Name(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - volDB := volBkt.Bucket([]byte(volume.Name())) - if volDB == nil { - volume.valid = false - 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 fmt.Errorf("updating volume %q config JSON: %w", volume.Name(), err) - } - - return nil - }) - return err -} - -// Pod retrieves a pod given its full ID -func (s *BoltState) Pod(id string) (*Pod, error) { - if id == "" { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - podID := []byte(id) - - pod := new(Pod) - pod.config = new(PodConfig) - pod.state = new(podState) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - return s.getPodFromDB(podID, pod, podBkt) - }) - if err != nil { - return nil, err - } - - return pod, nil -} - -// LookupPod retrieves a pod from full or unique partial ID or name -func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { - if idOrName == "" { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - pod := new(Pod) - pod.config = new(PodConfig) - pod.state = new(podState) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - namesBkt, err := getNamesBucket(tx) - if err != nil { - return err - } - - // First, check if the ID given was the actual pod ID - var id []byte - podExists := podBkt.Bucket([]byte(idOrName)) - if podExists != nil { - // A full pod ID was given. - id = []byte(idOrName) - return s.getPodFromDB(id, pod, podBkt) - } - - // Next, check if the full name was given - isCtr := false - fullID := namesBkt.Get([]byte(idOrName)) - if fullID != nil { - // The name exists and maps to an ID. - // However, we aren't yet sure if the ID is a pod. - podExists = podBkt.Bucket(fullID) - if podExists != nil { - // A pod bucket matching the full ID was found. - return s.getPodFromDB(fullID, pod, podBkt) - } - // Don't error if we have a name match but it's not a - // pod - there's a chance we have a pod with an ID - // starting with those characters. - // However, so we can return a good error, note whether - // this is a container. - isCtr = true - } - // They did not give us a full pod name or ID. - // Search for partial ID matches. - exists := false - err = podBkt.ForEach(func(checkID, _ []byte) error { - if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return fmt.Errorf("more than one result for ID or name %s: %w", idOrName, define.ErrPodExists) - } - id = checkID - exists = true - } - - return nil - }) - if err != nil { - return err - } else if !exists { - if isCtr { - return fmt.Errorf("%s is a container, not a pod: %w", idOrName, define.ErrNoSuchPod) - } - 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 - // We'll just fail in getPodFromDB with ErrNoSuchPod - return s.getPodFromDB(id, pod, podBkt) - }) - if err != nil { - return nil, err - } - - return pod, nil -} - -// HasPod checks if a pod with the given ID exists in the state -func (s *BoltState) HasPod(id string) (bool, error) { - if id == "" { - return false, define.ErrEmptyID - } - - if !s.valid { - return false, define.ErrDBClosed - } - - podID := []byte(id) - - exists := false - - db, err := s.getDBCon() - if err != nil { - return false, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - podDB := podBkt.Bucket(podID) - if podDB != nil { - exists = true - } - - return nil - }) - if err != nil { - return false, err - } - - return exists, nil -} - -// PodHasContainer checks if the given pod has a container with the given ID -func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { - if id == "" { - return false, define.ErrEmptyID - } - - if !s.valid { - return false, define.ErrDBClosed - } - - if !pod.valid { - return false, define.ErrPodRemoved - } - - ctrID := []byte(id) - podID := []byte(pod.ID()) - - exists := false - - db, err := s.getDBCon() - if err != nil { - return false, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - // Get pod itself - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - 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 fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) - } - - ctr := podCtrs.Get(ctrID) - if ctr != nil { - exists = true - } - - return nil - }) - if err != nil { - return false, err - } - - return exists, nil -} - -// PodContainersByID returns the IDs of all containers present in the given pod -func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !pod.valid { - return nil, define.ErrPodRemoved - } - - podID := []byte(pod.ID()) - - ctrs := []string{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - // Get pod itself - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - 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 fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) - } - - // Iterate through all containers in the pod - err = podCtrs.ForEach(func(id, _ []byte) error { - ctrs = append(ctrs, string(id)) - - return nil - }) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - return ctrs, nil -} - -// PodContainers returns all the containers present in the given pod -func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !pod.valid { - return nil, define.ErrPodRemoved - } - - podID := []byte(pod.ID()) - - ctrs := []*Container{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - ctrBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - // Get pod itself - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - 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 fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) - } - - // Iterate through all containers in the pod - err = podCtrs.ForEach(func(id, _ []byte) error { - newCtr := new(Container) - newCtr.config = new(ContainerConfig) - newCtr.state = new(ContainerState) - ctrs = append(ctrs, newCtr) - - return s.getContainerFromDB(id, newCtr, ctrBkt, false) - }) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - return ctrs, nil -} - -// AddVolume adds the given volume to the state. It also adds ctrDepID to -// the sub bucket holding the container dependencies that this volume has -func (s *BoltState) AddVolume(volume *Volume) error { - if !s.valid { - return define.ErrDBClosed - } - - if !volume.valid { - return define.ErrVolumeRemoved - } - - volName := []byte(volume.Name()) - - volConfigJSON, err := json.Marshal(volume.config) - if err != nil { - return fmt.Errorf("marshalling volume %s config to JSON: %w", volume.Name(), err) - } - - // Volume state is allowed to not exist - var volStateJSON []byte - if volume.state != nil { - volStateJSON, err = json.Marshal(volume.state) - if err != nil { - return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err) - } - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - allVolsBkt, err := getAllVolsBucket(tx) - if err != nil { - return err - } - - volCtrsBkt, err := getVolumeContainersBucket(tx) - if err != nil { - return err - } - - // Check if we already have a volume with the given name - volExists := allVolsBkt.Get(volName) - if volExists != nil { - 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 fmt.Errorf("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 fmt.Errorf("creating bucket for containers using volume %s: %w", volume.Name(), err) - } - - if err := newVol.Put(configKey, volConfigJSON); err != nil { - return fmt.Errorf("storing volume %s configuration in DB: %w", volume.Name(), err) - } - - if volStateJSON != nil { - if err := newVol.Put(stateKey, volStateJSON); err != nil { - return fmt.Errorf("storing volume %s state in DB: %w", volume.Name(), err) - } - } - - if volume.config.StorageID != "" { - if err := volCtrsBkt.Put([]byte(volume.config.StorageID), volName); err != nil { - return fmt.Errorf("storing volume %s container ID in DB: %w", volume.Name(), err) - } - } - - if err := allVolsBkt.Put(volName, volName); err != nil { - return fmt.Errorf("storing volume %s in all volumes bucket in DB: %w", volume.Name(), err) - } - - return nil - }) - return err -} - -// RemoveVolume removes the given volume from the state -func (s *BoltState) RemoveVolume(volume *Volume) error { - if !s.valid { - return define.ErrDBClosed - } - - volName := []byte(volume.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - allVolsBkt, err := getAllVolsBucket(tx) - if err != nil { - return err - } - - ctrBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - volCtrIDBkt, err := getVolumeContainersBucket(tx) - if err != nil { - return err - } - - // Check if the volume exists - volDB := volBkt.Bucket(volName) - if volDB == nil { - volume.valid = false - 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 - // This should never be nil - // But if it is, we can assume that no containers are using - // the volume. - volCtrsBkt := volDB.Bucket(volDependenciesBkt) - if volCtrsBkt != nil { - var deps []string - err = volCtrsBkt.ForEach(func(id, _ []byte) error { - // Alright, this is ugly. - // But we need it to work around the change in - // volume dependency handling, to make sure that - // older Podman versions don't cause DB - // corruption. - // Look up all dependencies and see that they - // still exist before appending. - ctrExists := ctrBkt.Bucket(id) - if ctrExists == nil { - return nil - } - - deps = append(deps, string(id)) - return nil - }) - if err != nil { - return fmt.Errorf("getting list of dependencies from dependencies bucket for volumes %q: %w", volume.Name(), err) - } - if len(deps) > 0 { - 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 fmt.Errorf("removing volume %s from all volumes bucket in DB: %w", volume.Name(), err) - } - if err := volBkt.DeleteBucket(volName); err != nil { - return fmt.Errorf("removing volume %s from DB: %w", volume.Name(), err) - } - if volume.config.StorageID != "" { - if err := volCtrIDBkt.Delete([]byte(volume.config.StorageID)); err != nil { - return fmt.Errorf("removing volume %s container ID from DB: %w", volume.Name(), err) - } - } - - return nil - }) - return err -} - -// UpdateVolume updates the volume's state from the database. -func (s *BoltState) UpdateVolume(volume *Volume) error { - if !s.valid { - return define.ErrDBClosed - } - - if !volume.valid { - return define.ErrVolumeRemoved - } - - newState := new(VolumeState) - volumeName := []byte(volume.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volBucket, err := getVolBucket(tx) - if err != nil { - return err - } - - volToUpdate := volBucket.Bucket(volumeName) - if volToUpdate == nil { - volume.valid = false - return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) - } - - stateBytes := volToUpdate.Get(stateKey) - if stateBytes == nil { - // Having no state is valid. - // Return nil, use the empty state. - return nil - } - - if err := json.Unmarshal(stateBytes, newState); err != nil { - return fmt.Errorf("unmarshalling volume %s state: %w", volume.Name(), err) - } - - return nil - }) - if err != nil { - return err - } - - volume.state = newState - - return nil -} - -// SaveVolume saves the volume's state to the database. -func (s *BoltState) SaveVolume(volume *Volume) error { - if !s.valid { - return define.ErrDBClosed - } - - if !volume.valid { - return define.ErrVolumeRemoved - } - - volumeName := []byte(volume.Name()) - - var newStateJSON []byte - if volume.state != nil { - stateJSON, err := json.Marshal(volume.state) - if err != nil { - return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err) - } - newStateJSON = stateJSON - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBucket, err := getVolBucket(tx) - if err != nil { - return err - } - - volToUpdate := volBucket.Bucket(volumeName) - if volToUpdate == nil { - volume.valid = false - return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) - } - - return volToUpdate.Put(stateKey, newStateJSON) - }) - return err -} - -// AllVolumes returns all volumes present in the state -func (s *BoltState) AllVolumes() ([]*Volume, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - volumes := []*Volume{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - allVolsBucket, err := getAllVolsBucket(tx) - if err != nil { - return err - } - - volBucket, err := getVolBucket(tx) - if err != nil { - return err - } - err = allVolsBucket.ForEach(func(id, _ []byte) error { - volExists := volBucket.Bucket(id) - // This check can be removed if performance becomes an - // issue, but much less helpful errors will be produced - if volExists == nil { - 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) - volume.config = new(VolumeConfig) - volume.state = new(VolumeState) - - if err := s.getVolumeFromDB(id, volume, volBucket); err != nil { - if !errors.Is(err, define.ErrNSMismatch) { - logrus.Errorf("Retrieving volume %s from the database: %v", string(id), err) - } - } else { - volumes = append(volumes, volume) - } - - return nil - }) - return err - }) - if err != nil { - return nil, err - } - - return volumes, nil -} - -// Volume retrieves a volume from full name -func (s *BoltState) Volume(name string) (*Volume, error) { - if name == "" { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - volName := []byte(name) - - volume := new(Volume) - volume.config = new(VolumeConfig) - volume.state = new(VolumeState) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - return s.getVolumeFromDB(volName, volume, volBkt) - }) - if err != nil { - return nil, err - } - - return volume, nil -} - -// LookupVolume locates a volume from a partial name. -func (s *BoltState) LookupVolume(name string) (*Volume, error) { - if name == "" { - return nil, define.ErrEmptyID - } - - if !s.valid { - return nil, define.ErrDBClosed - } - - volName := []byte(name) - - volume := new(Volume) - volume.config = new(VolumeConfig) - volume.state = new(VolumeState) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - allVolsBkt, err := getAllVolsBucket(tx) - if err != nil { - return err - } - - // Check for exact match on name - volDB := volBkt.Bucket(volName) - if volDB != nil { - return s.getVolumeFromDB(volName, volume, volBkt) - } - - // No exact match. Search all names. - foundMatch := false - err = allVolsBkt.ForEach(func(checkName, _ []byte) error { - if strings.HasPrefix(string(checkName), name) { - if foundMatch { - return fmt.Errorf("more than one result for volume name %q: %w", name, define.ErrVolumeExists) - } - foundMatch = true - volName = checkName - } - return nil - }) - if err != nil { - return err - } - - if !foundMatch { - return fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume) - } - - return s.getVolumeFromDB(volName, volume, volBkt) - }) - if err != nil { - return nil, err - } - - return volume, nil -} - -// HasVolume returns true if the given volume exists in the state, otherwise it returns false -func (s *BoltState) HasVolume(name string) (bool, error) { - if name == "" { - return false, define.ErrEmptyID - } - - if !s.valid { - return false, define.ErrDBClosed - } - - volName := []byte(name) - - exists := false - - db, err := s.getDBCon() - if err != nil { - return false, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - volDB := volBkt.Bucket(volName) - if volDB != nil { - exists = true - } - - return nil - }) - if err != nil { - return false, err - } - - return exists, nil -} - -// VolumeInUse checks if any container is using the volume -// It returns a slice of the IDs of the containers using the given -// volume. If the slice is empty, no containers use the given volume -func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !volume.valid { - return nil, define.ErrVolumeRemoved - } - - depCtrs := []string{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volBucket, err := getVolBucket(tx) - if err != nil { - return err - } - - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - volDB := volBucket.Bucket([]byte(volume.Name())) - if volDB == nil { - volume.valid = false - return fmt.Errorf("no volume with name %s found in DB: %w", volume.Name(), define.ErrNoSuchVolume) - } - - dependsBkt := volDB.Bucket(volDependenciesBkt) - if dependsBkt == nil { - return fmt.Errorf("volume %s has no dependencies bucket: %w", volume.Name(), define.ErrInternal) - } - - // Iterate through and add dependencies - err = dependsBkt.ForEach(func(id, _ []byte) error { - // Look up all dependencies and see that they - // still exist before appending. - ctrExists := ctrBucket.Bucket(id) - if ctrExists == nil { - return nil - } - - depCtrs = append(depCtrs, string(id)) - - return nil - }) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - return depCtrs, nil -} - -// AddPod adds the given pod to the state. -func (s *BoltState) AddPod(pod *Pod) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - podID := []byte(pod.ID()) - podName := []byte(pod.Name()) - - podConfigJSON, err := json.Marshal(pod.config) - if err != nil { - return fmt.Errorf("marshalling pod %s config to JSON: %w", pod.ID(), err) - } - - podStateJSON, err := json.Marshal(pod.state) - if err != nil { - return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - allPodsBkt, err := getAllPodsBucket(tx) - if err != nil { - return err - } - - idsBkt, err := getIDBucket(tx) - if err != nil { - return err - } - - namesBkt, err := getNamesBucket(tx) - if err != nil { - return err - } - - // Check if we already have something with the given ID and name - idExist := idsBkt.Get(podID) - if idExist != nil { - err = define.ErrPodExists - if allPodsBkt.Get(idExist) == nil { - err = define.ErrCtrExists - } - return fmt.Errorf("ID \"%s\" is in use: %w", pod.ID(), err) - } - nameExist := namesBkt.Get(podName) - if nameExist != nil { - err = define.ErrPodExists - if allPodsBkt.Get(nameExist) == nil { - err = define.ErrCtrExists - } - 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 fmt.Errorf("creating bucket for pod %s: %w", pod.ID(), err) - } - - // Make a subbucket for pod containers - if _, err := newPod.CreateBucket(containersBkt); err != nil { - return fmt.Errorf("creating bucket for pod %s containers: %w", pod.ID(), err) - } - - if err := newPod.Put(configKey, podConfigJSON); err != nil { - return fmt.Errorf("storing pod %s configuration in DB: %w", pod.ID(), err) - } - - if err := newPod.Put(stateKey, podStateJSON); err != nil { - return fmt.Errorf("storing pod %s state JSON in DB: %w", pod.ID(), err) - } - - // Add us to the ID and names buckets - if err := idsBkt.Put(podID, podName); err != nil { - return fmt.Errorf("storing pod %s ID in DB: %w", pod.ID(), err) - } - if err := namesBkt.Put(podName, podID); err != nil { - return fmt.Errorf("storing pod %s name in DB: %w", pod.Name(), err) - } - if err := allPodsBkt.Put(podID, podName); err != nil { - return fmt.Errorf("storing pod %s in all pods bucket in DB: %w", pod.ID(), err) - } - - return nil - }) - if err != nil { - return err - } - - return nil -} - -// RemovePod removes the given pod from the state -// Only empty pods can be removed -func (s *BoltState) RemovePod(pod *Pod) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - podID := []byte(pod.ID()) - podName := []byte(pod.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - allPodsBkt, err := getAllPodsBucket(tx) - if err != nil { - return err - } - - idsBkt, err := getIDBucket(tx) - if err != nil { - return err - } - - namesBkt, err := getNamesBucket(tx) - if err != nil { - return err - } - - // Check if the pod exists - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) - } - - // Check if pod is empty - // This should never be nil - // But if it is, we can assume there are no containers in the - // pod. - // So let's eject the malformed pod without error. - podCtrsBkt := podDB.Bucket(containersBkt) - if podCtrsBkt != nil { - cursor := podCtrsBkt.Cursor() - if id, _ := cursor.First(); id != nil { - 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 fmt.Errorf("removing pod %s ID from DB: %w", pod.ID(), err) - } - if err := namesBkt.Delete(podName); err != nil { - return fmt.Errorf("removing pod %s name (%s) from DB: %w", pod.ID(), pod.Name(), err) - } - if err := allPodsBkt.Delete(podID); err != nil { - return fmt.Errorf("removing pod %s ID from all pods bucket in DB: %w", pod.ID(), err) - } - if err := podBkt.DeleteBucket(podID); err != nil { - return fmt.Errorf("removing pod %s from DB: %w", pod.ID(), err) - } - - return nil - }) - if err != nil { - return err - } - - return nil -} - -// RemovePodContainers removes all containers in a pod -func (s *BoltState) RemovePodContainers(pod *Pod) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - podID := []byte(pod.ID()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - ctrBkt, err := getCtrBucket(tx) - if err != nil { - return err - } - - allCtrsBkt, err := getAllCtrsBucket(tx) - if err != nil { - return err - } - - idsBkt, err := getIDBucket(tx) - if err != nil { - return err - } - - namesBkt, err := getNamesBucket(tx) - if err != nil { - return err - } - - // Check if the pod exists - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) - } - - podCtrsBkt := podDB.Bucket(containersBkt) - if podCtrsBkt == nil { - 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 - // for-each has issues with data mutation - err = podCtrsBkt.ForEach(func(id, name []byte) error { - // Get the container so we can check dependencies - ctr := ctrBkt.Bucket(id) - if ctr == nil { - // This should never happen - // State is inconsistent - 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 - // removing it anyways, so continue if it is - if ctrDeps != nil { - err = ctrDeps.ForEach(func(depID, _ []byte) error { - exists := podCtrsBkt.Get(depID) - if exists == nil { - return fmt.Errorf("container %s has dependency %s outside of pod %s: %w", string(id), string(depID), pod.ID(), define.ErrCtrExists) - } - return nil - }) - if err != nil { - return err - } - } - - // Dependencies are set, we're clear to remove - - if err := ctrBkt.DeleteBucket(id); err != nil { - return fmt.Errorf("deleting container %s from DB: %w", string(id), define.ErrInternal) - } - - if err := idsBkt.Delete(id); err != nil { - return fmt.Errorf("deleting container %s ID in DB: %w", string(id), err) - } - - if err := namesBkt.Delete(name); err != nil { - return fmt.Errorf("deleting container %s name in DB: %w", string(id), err) - } - - if err := allCtrsBkt.Delete(id); err != nil { - return fmt.Errorf("deleting container %s ID from all containers bucket in DB: %w", string(id), err) - } - - return nil - }) - if err != nil { - return err - } - - // Delete and recreate the bucket to empty it - if err := podDB.DeleteBucket(containersBkt); err != nil { - return fmt.Errorf("removing pod %s containers bucket: %w", pod.ID(), err) - } - if _, err := podDB.CreateBucket(containersBkt); err != nil { - return fmt.Errorf("recreating pod %s containers bucket: %w", pod.ID(), err) - } - - return nil - }) - if err != nil { - return err - } - - return nil -} - -// 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 { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if ctr.config.Pod != 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) -} - -// 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 { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - if ctr.config.Pod == "" { - 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 fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - return s.removeContainer(ctr, pod, tx) - }) - return err -} - -// UpdatePod updates a pod's state from the database -func (s *BoltState) UpdatePod(pod *Pod) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - newState := new(podState) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - podID := []byte(pod.ID()) - - err = db.View(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - return 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 fmt.Errorf("pod %s is missing state key in DB: %w", pod.ID(), define.ErrInternal) - } - - if err := json.Unmarshal(podStateBytes, newState); err != nil { - return fmt.Errorf("unmarshalling pod %s state JSON: %w", pod.ID(), err) - } - - return nil - }) - if err != nil { - return err - } - - pod.state = newState - - return nil -} - -// SavePod saves a pod's state to the database -func (s *BoltState) SavePod(pod *Pod) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - stateJSON, err := json.Marshal(pod.state) - if err != nil { - return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err) - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - podID := []byte(pod.ID()) - - err = db.Update(func(tx *bolt.Tx) error { - podBkt, err := getPodBucket(tx) - if err != nil { - return err - } - - podDB := podBkt.Bucket(podID) - if podDB == nil { - pod.valid = false - return 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 fmt.Errorf("updating pod %s state in database: %w", pod.ID(), err) - } - - return nil - }) - if err != nil { - return err - } - - return nil -} - -// AllPods returns all pods present in the state -func (s *BoltState) AllPods() ([]*Pod, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - pods := []*Pod{} - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - allPodsBucket, err := getAllPodsBucket(tx) - if err != nil { - return err - } - - podBucket, err := getPodBucket(tx) - if err != nil { - return err - } - - err = allPodsBucket.ForEach(func(id, _ []byte) error { - podExists := podBucket.Bucket(id) - // This check can be removed if performance becomes an - // issue, but much less helpful errors will be produced - if podExists == nil { - 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) - pod.config = new(PodConfig) - pod.state = new(podState) - - if err := s.getPodFromDB(id, pod, podBucket); err != nil { - if !errors.Is(err, define.ErrNSMismatch) { - logrus.Errorf("Retrieving pod %s from the database: %v", string(id), err) - } - } else { - pods = append(pods, pod) - } - - return nil - }) - return err - }) - if err != nil { - return nil, err - } - - return pods, nil -} - -// ContainerIDIsVolume checks if the given c/storage container ID is used as -// backing storage for a volume. -func (s *BoltState) ContainerIDIsVolume(id string) (bool, error) { - if !s.valid { - return false, define.ErrDBClosed - } - - isVol := false - - db, err := s.getDBCon() - if err != nil { - return false, err - } - defer s.deferredCloseDBCon(db) - - err = db.View(func(tx *bolt.Tx) error { - volCtrsBkt, err := getVolumeContainersBucket(tx) - if err != nil { - return err - } - - volName := volCtrsBkt.Get([]byte(id)) - if volName != nil { - isVol = true - } - - return nil - }) - return isVol, err -} diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go deleted file mode 100644 index 1bbdd95c5d..0000000000 --- a/libpod/boltdb_state_internal.go +++ /dev/null @@ -1,1058 +0,0 @@ -//go:build !remote - -package libpod - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/containers/podman/v6/libpod/define" - "github.com/sirupsen/logrus" - bolt "go.etcd.io/bbolt" - "go.podman.io/storage" -) - -const ( - idRegistryName = "id-registry" - nameRegistryName = "name-registry" - ctrName = "ctr" - allCtrsName = "all-ctrs" - podName = "pod" - allPodsName = "allPods" - volName = "vol" - allVolsName = "allVolumes" - execName = "exec" - aliasesName = "aliases" - runtimeConfigName = "runtime-config" - volumeCtrsName = "volume-ctrs" - - exitCodeName = "exit-code" - exitCodeTimeStampName = "exit-code-time-stamp" - - configName = "config" - stateName = "state" - dependenciesName = "dependencies" - volCtrDependencies = "vol-dependencies" - netNSName = "netns" - containersName = "containers" - podIDName = "pod-id" - networksName = "networks" - - staticDirName = "static-dir" - tmpDirName = "tmp-dir" - runRootName = "run-root" - graphRootName = "graph-root" - graphDriverName = "graph-driver-name" - osName = "os" - volPathName = "volume-path" -) - -var ( - idRegistryBkt = []byte(idRegistryName) - nameRegistryBkt = []byte(nameRegistryName) - ctrBkt = []byte(ctrName) - allCtrsBkt = []byte(allCtrsName) - podBkt = []byte(podName) - allPodsBkt = []byte(allPodsName) - volBkt = []byte(volName) - allVolsBkt = []byte(allVolsName) - execBkt = []byte(execName) - aliasesBkt = []byte(aliasesName) - runtimeConfigBkt = []byte(runtimeConfigName) - dependenciesBkt = []byte(dependenciesName) - volDependenciesBkt = []byte(volCtrDependencies) - networksBkt = []byte(networksName) - volCtrsBkt = []byte(volumeCtrsName) - - exitCodeBkt = []byte(exitCodeName) - exitCodeTimeStampBkt = []byte(exitCodeTimeStampName) - - configKey = []byte(configName) - stateKey = []byte(stateName) - netNSKey = []byte(netNSName) - containersBkt = []byte(containersName) - podIDKey = []byte(podIDName) - - staticDirKey = []byte(staticDirName) - tmpDirKey = []byte(tmpDirName) - runRootKey = []byte(runRootName) - graphRootKey = []byte(graphRootName) - graphDriverKey = []byte(graphDriverName) - osKey = []byte(osName) - volPathKey = []byte(volPathName) -) - -// This represents a field in the runtime configuration that will be validated -// against the DB to ensure no configuration mismatches occur. -type dbConfigValidation struct { - name string // Only used for error messages - runtimeValue string - key []byte - defaultValue string - isPath bool -} - -// 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, rt *Runtime) error { - storeOpts, err := storage.DefaultStoreOptions() - if err != nil { - return err - } - - // We need to validate the following things - checks := []dbConfigValidation{ - { - "OS", - runtime.GOOS, - osKey, - runtime.GOOS, - false, - }, - { - "libpod root directory (staticdir)", - filepath.Clean(rt.config.Engine.StaticDir), - staticDirKey, - "", - true, - }, - { - "libpod temporary files directory (tmpdir)", - filepath.Clean(rt.config.Engine.TmpDir), - tmpDirKey, - "", - true, - }, - { - "storage temporary directory (runroot)", - filepath.Clean(rt.StorageConfig().RunRoot), - runRootKey, - storeOpts.RunRoot, - true, - }, - { - "storage graph root directory (graphroot)", - filepath.Clean(rt.StorageConfig().GraphRoot), - graphRootKey, - storeOpts.GraphRoot, - true, - }, - { - "storage graph driver", - rt.StorageConfig().GraphDriverName, - graphDriverKey, - storeOpts.GraphDriverName, - false, - }, - { - "volume path", - rt.config.Engine.VolumePath, - volPathKey, - "", - true, - }, - } - - // These fields were missing and will have to be recreated. - missingFields := []dbConfigValidation{} - - // Let's try and validate read-only first - err = db.View(func(tx *bolt.Tx) error { - configBkt, err := getRuntimeConfigBucket(tx) - if err != nil { - return err - } - - for _, check := range checks { - exists, err := readOnlyValidateConfig(configBkt, check) - if err != nil { - return err - } - if !exists { - missingFields = append(missingFields, check) - } - } - - return nil - }) - if err != nil { - return err - } - - if len(missingFields) == 0 { - return nil - } - - // Populate missing fields - return db.Update(func(tx *bolt.Tx) error { - configBkt, err := getRuntimeConfigBucket(tx) - if err != nil { - return err - } - - for _, missing := range missingFields { - dbValue := []byte(missing.runtimeValue) - if missing.runtimeValue == "" && missing.defaultValue != "" { - dbValue = []byte(missing.defaultValue) - } - - if err := configBkt.Put(missing.key, dbValue); err != nil { - return fmt.Errorf("updating %s in DB runtime config: %w", missing.name, err) - } - } - - return nil - }) -} - -// Attempt a read-only validation of a configuration entry in the DB against an -// element of the current runtime configuration. -// If the configuration key in question does not exist, (false, nil) will be -// returned. -// If the configuration key does exist, and matches the runtime configuration -// successfully, (true, nil) is returned. -// An error is only returned when validation fails. -// if the given runtimeValue or value retrieved from the database are empty, -// and defaultValue is not, defaultValue will be checked instead. This ensures -// that we will not fail on configuration changes in c/storage (where we may -// pass the empty string to use defaults). -func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) { - keyBytes := bucket.Get(toCheck.key) - if keyBytes == nil { - // False return indicates missing key - return false, nil - } - - dbValue := string(keyBytes) - ourValue := toCheck.runtimeValue - - // Tolerate symlinks when possible - most relevant for OStree systems - // and rootless containers, where we want to put containers in /home, - // which is symlinked to /var/home. - if toCheck.isPath { - if dbValue != "" { - checkedVal, err := evalSymlinksIfExists(dbValue) - if err != nil { - return false, fmt.Errorf("evaluating symlinks on DB %s path %q: %w", toCheck.name, dbValue, err) - } - dbValue = checkedVal - } - if ourValue != "" { - checkedVal, err := evalSymlinksIfExists(ourValue) - if err != nil { - return false, fmt.Errorf("evaluating symlinks on configured %s path %q: %w", toCheck.name, ourValue, err) - } - ourValue = checkedVal - } - } - - if ourValue != dbValue { - // If the runtime value is the empty string and default is not, - // check against default. - if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { - return true, nil - } - - // If the DB value is the empty string, check that the runtime - // value is the default. - if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue { - return true, nil - } - - return true, fmt.Errorf("database %s %q does not match our %s %q: %w", - toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig) - } - - return true, nil -} - -// Open a connection to the database. -// Must be paired with a `defer closeDBCon()` on the returned database, to -// ensure the state is properly unlocked -func (s *BoltState) getDBCon() (*bolt.DB, error) { - // We need an in-memory lock to avoid issues around POSIX file advisory - // locks as described in the link below: - // https://www.sqlite.org/src/artifact/c230a7a24?ln=994-1081 - s.dbLock.Lock() - - db, err := bolt.Open(s.dbPath, 0o600, nil) - if err != nil { - return nil, fmt.Errorf("opening database %s: %w", s.dbPath, err) - } - - return db, nil -} - -// deferredCloseDBCon closes the bolt db but instead of returning an -// error it logs the error. it is meant to be used within the confines -// of a defer statement only -func (s *BoltState) deferredCloseDBCon(db *bolt.DB) { - if err := s.closeDBCon(db); err != nil { - logrus.Errorf("Failed to close libpod db: %q", err) - } -} - -// Close a connection to the database. -// MUST be used in place of `db.Close()` to ensure proper unlocking of the -// state. -func (s *BoltState) closeDBCon(db *bolt.DB) error { - err := db.Close() - - s.dbLock.Unlock() - - return err -} - -func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(idRegistryBkt) - if bkt == nil { - return nil, fmt.Errorf("id registry bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(nameRegistryBkt) - if bkt == nil { - return nil, fmt.Errorf("name registry bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(ctrBkt) - if bkt == nil { - return nil, fmt.Errorf("containers bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getAllCtrsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(allCtrsBkt) - if bkt == nil { - return nil, fmt.Errorf("all containers bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getPodBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(podBkt) - if bkt == nil { - return nil, fmt.Errorf("pods bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getAllPodsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(allPodsBkt) - if bkt == nil { - return nil, fmt.Errorf("all pods bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getVolBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(volBkt) - if bkt == nil { - return nil, fmt.Errorf("volumes bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getAllVolsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(allVolsBkt) - if bkt == nil { - return nil, fmt.Errorf("all volumes bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getExecBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(execBkt) - if bkt == nil { - return nil, fmt.Errorf("exec bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(runtimeConfigBkt) - if bkt == nil { - return nil, fmt.Errorf("runtime configuration bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(exitCodeBkt) - if bkt == nil { - return nil, fmt.Errorf("exit-code container bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(exitCodeTimeStampBkt) - if bkt == nil { - return nil, fmt.Errorf("exit-code time stamp bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func getVolumeContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bkt := tx.Bucket(volCtrsBkt) - if bkt == nil { - return nil, fmt.Errorf("volume containers bucket not found in DB: %w", define.ErrDBBadConfig) - } - return bkt, nil -} - -func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error { - ctrBkt := ctrsBkt.Bucket(id) - if ctrBkt == nil { - return fmt.Errorf("container %s not found in DB: %w", string(id), define.ErrNoSuchCtr) - } - - configBytes := ctrBkt.Get(configKey) - if configBytes == nil { - return fmt.Errorf("container %s missing config key in DB: %w", string(id), define.ErrInternal) - } - - if err := json.Unmarshal(configBytes, config); err != nil { - return fmt.Errorf("unmarshalling container %s config: %w", string(id), err) - } - - // convert ports to the new format if needed - if len(config.ContainerNetworkConfig.OldPortMappings) > 0 && len(config.ContainerNetworkConfig.PortMappings) == 0 { - config.ContainerNetworkConfig.PortMappings = ocicniPortsToNetTypesPorts(config.ContainerNetworkConfig.OldPortMappings) - // keep the OldPortMappings in case an user has to downgrade podman - - // indicate that the config was modified and should be written back to the db when possible - config.rewrite = true - } - - return nil -} - -func (s *BoltState) getContainerStateDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error { - newState := new(ContainerState) - ctrToUpdate := ctrsBkt.Bucket(id) - if ctrToUpdate == nil { - ctr.valid = false - return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - newStateBytes := ctrToUpdate.Get(stateKey) - if newStateBytes == nil { - 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 fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err) - } - - // backwards compat, previously we used an extra bucket for the netns so try to get it from there - netNSBytes := ctrToUpdate.Get(netNSKey) - if netNSBytes != nil && newState.NetNS == "" { - newState.NetNS = string(netNSBytes) - } - - // New state compiled successfully, swap it into the current state - ctr.state = newState - return nil -} - -func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket, loadState bool) error { - if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil { - return err - } - - if loadState { - if err := s.getContainerStateDB(id, ctr, ctrsBkt); err != nil { - return err - } - } - - // Get the lock - lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID) - if err != nil { - return fmt.Errorf("retrieving lock for container %s: %w", string(id), err) - } - ctr.lock = lock - - if ctr.config.OCIRuntime == "" { - ctr.ociRuntime = s.runtime.defaultOCIRuntime - } else { - // Handle legacy containers which might use a literal path for - // their OCI runtime name. - runtimeName := ctr.config.OCIRuntime - ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] - if !ok { - runtimeSet := false - - // If the path starts with a / and exists, make a new - // OCI runtime for it using the full path. - if strings.HasPrefix(runtimeName, "/") { - if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() { - newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, s.runtime.conmonPath, s.runtime.runtimeFlags, s.runtime.config) - if err == nil { - // The runtime lock should - // protect against concurrent - // modification of the map. - ociRuntime = newOCIRuntime - s.runtime.ociRuntimes[runtimeName] = ociRuntime - runtimeSet = true - } - } - } - - if !runtimeSet { - // Use a MissingRuntime implementation - ociRuntime = getMissingRuntime(runtimeName, s.runtime) - } - } - ctr.ociRuntime = ociRuntime - } - - ctr.runtime = s.runtime - ctr.valid = true - - return nil -} - -func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error { - podDB := podBkt.Bucket(id) - if podDB == nil { - return fmt.Errorf("pod with ID %s not found: %w", string(id), define.ErrNoSuchPod) - } - - podConfigBytes := podDB.Get(configKey) - if podConfigBytes == nil { - return fmt.Errorf("pod %s is missing configuration key in DB: %w", string(id), define.ErrInternal) - } - - if err := json.Unmarshal(podConfigBytes, pod.config); err != nil { - return fmt.Errorf("unmarshalling pod %s config from DB: %w", string(id), err) - } - - // Get the lock - lock, err := s.runtime.lockManager.RetrieveLock(pod.config.LockID) - if err != nil { - return fmt.Errorf("retrieving lock for pod %s: %w", string(id), err) - } - pod.lock = lock - - pod.runtime = s.runtime - pod.valid = true - - return nil -} - -func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bucket) error { - volDB := volBkt.Bucket(name) - if volDB == nil { - return fmt.Errorf("volume with name %s not found: %w", string(name), define.ErrNoSuchVolume) - } - - volConfigBytes := volDB.Get(configKey) - if volConfigBytes == nil { - return fmt.Errorf("volume %s is missing configuration key in DB: %w", string(name), define.ErrInternal) - } - - if err := json.Unmarshal(volConfigBytes, volume.config); err != nil { - return fmt.Errorf("unmarshalling volume %s config from DB: %w", string(name), err) - } - - // Volume state is allowed to be nil for legacy compatibility - volStateBytes := volDB.Get(stateKey) - if volStateBytes != nil { - if err := json.Unmarshal(volStateBytes, volume.state); err != nil { - return fmt.Errorf("unmarshalling volume %s state from DB: %w", string(name), err) - } - } - - // Need this for UsesVolumeDriver() so set it now. - volume.runtime = s.runtime - - // Retrieve volume driver - if volume.UsesVolumeDriver() { - plugin, err := s.runtime.getVolumePlugin(volume.config) - if err != nil { - // We want to fail gracefully here, to ensure that we - // can still remove volumes even if their plugin is - // missing. Otherwise, we end up with volumes that - // cannot even be retrieved from the database and will - // cause things like `volume ls` to fail. - logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", volume.Name(), volume.config.Driver, err) - } else { - volume.plugin = plugin - } - } - - // Get the lock - lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID) - if err != nil { - return fmt.Errorf("retrieving lock for volume %q: %w", string(name), err) - } - volume.lock = lock - - volume.valid = true - - return nil -} - -// Add a container to the DB -// If pod is not nil, the container is added to the pod as well -func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { - // Set the original networks to nil. We can save some space by not storing it in the config - // since we store it in a different mutable bucket anyway. - configNetworks := ctr.config.Networks - ctr.config.Networks = nil - - // JSON container structs to insert into DB - configJSON, err := json.Marshal(ctr.config) - if err != nil { - return fmt.Errorf("marshalling container %s config to JSON: %w", ctr.ID(), err) - } - stateJSON, err := json.Marshal(ctr.state) - if err != nil { - return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err) - } - dependsCtrs := ctr.Dependencies() - - ctrID := []byte(ctr.ID()) - ctrName := []byte(ctr.Name()) - - // make sure to marshal the network options before we get the db lock - networks := make(map[string][]byte, len(configNetworks)) - for net, opts := range configNetworks { - // Check that we don't have any empty network names - if net == "" { - return fmt.Errorf("network names cannot be an empty string: %w", define.ErrInvalidArg) - } - if opts.InterfaceName == "" { - return fmt.Errorf("network interface name cannot be an empty string: %w", define.ErrInvalidArg) - } - optBytes, err := json.Marshal(opts) - if err != nil { - return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err) - } - networks[net] = optBytes - } - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.deferredCloseDBCon(db) - - 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 - } - - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - allCtrsBucket, err := getAllCtrsBucket(tx) - if err != nil { - return err - } - - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - // If a pod was given, check if it exists - var podDB *bolt.Bucket - var podCtrs *bolt.Bucket - if pod != nil { - podBucket, err := getPodBucket(tx) - if err != nil { - return err - } - - podID := []byte(pod.ID()) - - podDB = podBucket.Bucket(podID) - if podDB == nil { - pod.valid = false - return fmt.Errorf("pod %s does not exist in database: %w", pod.ID(), define.ErrNoSuchPod) - } - podCtrs = podDB.Bucket(containersBkt) - if podCtrs == nil { - return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal) - } - } - - // Check if we already have a container with the given ID and name - idExist := idsBucket.Get(ctrID) - if idExist != nil { - err = define.ErrCtrExists - if allCtrsBucket.Get(idExist) == nil { - err = define.ErrPodExists - } - return fmt.Errorf("ID \"%s\" is in use: %w", ctr.ID(), err) - } - nameExist := namesBucket.Get(ctrName) - if nameExist != nil { - err = define.ErrCtrExists - if allCtrsBucket.Get(nameExist) == nil { - err = define.ErrPodExists - } - return fmt.Errorf("name \"%s\" is in use: %w", ctr.Name(), err) - } - - // No overlapping containers - // Add the new container to the DB - if err := idsBucket.Put(ctrID, ctrName); err != nil { - return fmt.Errorf("adding container %s ID to DB: %w", ctr.ID(), err) - } - if err := namesBucket.Put(ctrName, ctrID); err != nil { - return fmt.Errorf("adding container %s name (%s) to DB: %w", ctr.ID(), ctr.Name(), err) - } - if err := allCtrsBucket.Put(ctrID, ctrName); err != nil { - return fmt.Errorf("adding container %s to all containers bucket in DB: %w", ctr.ID(), err) - } - - newCtrBkt, err := ctrBucket.CreateBucket(ctrID) - if err != nil { - return fmt.Errorf("adding container %s bucket to DB: %w", ctr.ID(), err) - } - - if err := newCtrBkt.Put(configKey, configJSON); err != nil { - return fmt.Errorf("adding container %s config to DB: %w", ctr.ID(), err) - } - if err := newCtrBkt.Put(stateKey, stateJSON); err != nil { - return fmt.Errorf("adding container %s state to DB: %w", ctr.ID(), err) - } - if pod != nil { - if err := newCtrBkt.Put(podIDKey, []byte(pod.ID())); err != nil { - return fmt.Errorf("adding container %s pod to DB: %w", ctr.ID(), err) - } - } - if len(networks) > 0 { - ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt) - if err != nil { - return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err) - } - for network, opts := range networks { - if err := ctrNetworksBkt.Put([]byte(network), opts); err != nil { - return fmt.Errorf("adding network %q to networks bucket for container %s: %w", network, ctr.ID(), err) - } - } - } - - if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil { - return fmt.Errorf("creating dependencies bucket for container %s: %w", ctr.ID(), err) - } - - // Add dependencies for the container - for _, dependsCtr := range dependsCtrs { - depCtrID := []byte(dependsCtr) - - depCtrBkt := ctrBucket.Bucket(depCtrID) - if depCtrBkt == nil { - return fmt.Errorf("container %s depends on container %s, but it does not exist in the DB: %w", ctr.ID(), dependsCtr, define.ErrNoSuchCtr) - } - - depCtrPod := depCtrBkt.Get(podIDKey) - if pod != nil { - // If we're part of a pod, make sure the dependency is part of the same pod - if depCtrPod == nil { - return fmt.Errorf("container %s depends on container %s which is not in pod %s: %w", ctr.ID(), dependsCtr, pod.ID(), define.ErrInvalidArg) - } - - if string(depCtrPod) != pod.ID() { - return fmt.Errorf("container %s depends on container %s which is in a different pod (%s): %w", ctr.ID(), dependsCtr, string(depCtrPod), define.ErrInvalidArg) - } - } else if depCtrPod != nil { - // If we're not part of a pod, we cannot depend on containers in a pod - return fmt.Errorf("container %s depends on container %s which is in a pod - containers not in pods cannot depend on containers in pods: %w", ctr.ID(), dependsCtr, define.ErrInvalidArg) - } - - depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt) - if depCtrDependsBkt == nil { - return fmt.Errorf("container %s does not have a dependencies bucket: %w", dependsCtr, define.ErrInternal) - } - if err := depCtrDependsBkt.Put(ctrID, ctrName); err != nil { - return fmt.Errorf("adding ctr %s as dependency of container %s: %w", ctr.ID(), dependsCtr, err) - } - } - - // Add ctr to pod - if pod != nil && podCtrs != nil { - if err := podCtrs.Put(ctrID, ctrName); err != nil { - return fmt.Errorf("adding container %s to pod %s: %w", ctr.ID(), pod.ID(), err) - } - } - - // Add container to named volume dependencies buckets - for _, vol := range ctr.config.NamedVolumes { - volDB := volBkt.Bucket([]byte(vol.Name)) - if volDB == nil { - return fmt.Errorf("no volume with name %s found in database when adding container %s: %w", vol.Name, ctr.ID(), define.ErrNoSuchVolume) - } - - ctrDepsBkt, err := volDB.CreateBucketIfNotExists(volDependenciesBkt) - if err != nil { - return fmt.Errorf("creating volume %s dependencies bucket to add container %s: %w", vol.Name, ctr.ID(), err) - } - if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { - if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { - return fmt.Errorf("adding container %s to volume %s dependencies: %w", ctr.ID(), vol.Name, err) - } - } - } - - return nil - }) - return err -} - -// Remove a container from the DB -// If pod is not nil, the container is treated as belonging to a pod, and -// will be removed from the pod as well -func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { - ctrID := []byte(ctr.ID()) - ctrName := []byte(ctr.Name()) - - idsBucket, err := getIDBucket(tx) - if err != nil { - return err - } - - namesBucket, err := getNamesBucket(tx) - if err != nil { - return err - } - - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - allCtrsBucket, err := getAllCtrsBucket(tx) - if err != nil { - return err - } - - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - // Does the pod exist? - var podDB *bolt.Bucket - if pod != nil { - podBucket, err := getPodBucket(tx) - if err != nil { - return err - } - - podID := []byte(pod.ID()) - - podDB = podBucket.Bucket(podID) - if podDB == nil { - pod.valid = false - return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod) - } - } - - // Does the container exist? - ctrExists := ctrBucket.Bucket(ctrID) - if ctrExists == nil { - ctr.valid = false - return fmt.Errorf("no container with ID %s found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) - } - - if podDB != nil && pod != nil { - // Check if the container is in the pod, remove it if it is - podCtrs := podDB.Bucket(containersBkt) - if podCtrs == nil { - // Malformed pod - logrus.Errorf("Pod %s malformed in database, missing containers bucket!", pod.ID()) - } else { - ctrInPod := podCtrs.Get(ctrID) - if ctrInPod == nil { - return fmt.Errorf("container %s is not in pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) - } - if err := podCtrs.Delete(ctrID); err != nil { - return fmt.Errorf("removing container %s from pod %s: %w", ctr.ID(), pod.ID(), err) - } - } - } - - // Does the container have exec sessions? - ctrExecSessionsBkt := ctrExists.Bucket(execBkt) - if ctrExecSessionsBkt != nil { - sessions := []string{} - err = ctrExecSessionsBkt.ForEach(func(id, _ []byte) error { - sessions = append(sessions, string(id)) - - return nil - }) - if err != nil { - return err - } - if len(sessions) > 0 { - return fmt.Errorf("container %s has active exec sessions: %s: %w", ctr.ID(), strings.Join(sessions, ", "), define.ErrExecSessionExists) - } - } - - // Does the container have dependencies? - ctrDepsBkt := ctrExists.Bucket(dependenciesBkt) - if ctrDepsBkt == nil { - return fmt.Errorf("container %s does not have a dependencies bucket: %w", ctr.ID(), define.ErrInternal) - } - deps := []string{} - err = ctrDepsBkt.ForEach(func(id, _ []byte) error { - deps = append(deps, string(id)) - - return nil - }) - if err != nil { - return err - } - if len(deps) != 0 { - return fmt.Errorf("container %s is a dependency of the following containers: %s: %w", ctr.ID(), strings.Join(deps, ", "), define.ErrDepExists) - } - - if err := ctrBucket.DeleteBucket(ctrID); err != nil { - return fmt.Errorf("deleting container %s from DB: %w", ctr.ID(), define.ErrInternal) - } - - if err := idsBucket.Delete(ctrID); err != nil { - return fmt.Errorf("deleting container %s ID in DB: %w", ctr.ID(), err) - } - - if err := namesBucket.Delete(ctrName); err != nil { - return fmt.Errorf("deleting container %s name in DB: %w", ctr.ID(), err) - } - if err := allCtrsBucket.Delete(ctrID); err != nil { - return fmt.Errorf("deleting container %s from all containers bucket in DB: %w", ctr.ID(), err) - } - - depCtrs := ctr.Dependencies() - - // Remove us from other container's dependencies - for _, depCtr := range depCtrs { - depCtrID := []byte(depCtr) - - depCtrBkt := ctrBucket.Bucket(depCtrID) - if depCtrBkt == nil { - // The dependent container has been removed - // This should not be possible, and means the - // state is inconsistent, but don't error - // The container with inconsistent state is the - // one being removed - continue - } - - depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt) - if depCtrDependsBkt == nil { - // This is more serious - another container in - // the state is inconsistent - // Log it, continue removing - logrus.Errorf("Container %s is missing dependencies bucket in DB", ctr.ID()) - continue - } - - if err := depCtrDependsBkt.Delete(ctrID); err != nil { - return fmt.Errorf("removing container %s as a dependency of container %s: %w", ctr.ID(), depCtr, err) - } - } - - // Remove container from named volume dependencies buckets - for _, vol := range ctr.config.NamedVolumes { - volDB := volBkt.Bucket([]byte(vol.Name)) - if volDB == nil { - // Let's assume the volume was already deleted and - // continue to remove the container - continue - } - - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if ctrDepsBkt == nil { - return fmt.Errorf("volume %s is missing container dependencies bucket, cannot remove container %s from dependencies: %w", vol.Name, ctr.ID(), define.ErrInternal) - } - if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { - if err := ctrDepsBkt.Delete(ctrID); err != nil { - return fmt.Errorf("deleting container %s dependency on volume %s: %w", ctr.ID(), vol.Name, err) - } - } - } - - return nil -} - -// lookupContainerID retrieves a container ID from the state by full or unique -// partial ID or name. -func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket *bolt.Bucket) ([]byte, error) { - // First, check if the ID given was the actual container ID - ctrExists := ctrBucket.Bucket([]byte(idOrName)) - if ctrExists != nil { - // A full container ID was given. - return []byte(idOrName), nil - } - - // Next, check if the full name was given - isPod := false - fullID := namesBucket.Get([]byte(idOrName)) - if fullID != nil { - // The name exists and maps to an ID. - // However, we are not yet certain the ID is a - // container. - ctrExists = ctrBucket.Bucket(fullID) - if ctrExists != nil { - // A container bucket matching the full ID was - // found. - return fullID, nil - } - // Don't error if we have a name match but it's not a - // container - there's a chance we have a container with - // an ID starting with those characters. - // However, so we can return a good error, note whether - // this is a pod. - isPod = true - } - - var id []byte - // We were not given a full container ID or name. - // Search for partial ID matches. - exists := false - err := ctrBucket.ForEach(func(checkID, _ []byte) error { - if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return fmt.Errorf("more than one result for container ID %s: %w", idOrName, define.ErrCtrExists) - } - id = checkID - exists = true - } - - return nil - }) - - if err != nil { - return nil, err - } else if !exists { - if isPod { - return nil, fmt.Errorf("%q is a pod, not a container: %w", idOrName, define.ErrNoSuchCtr) - } - return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr) - } - return id, nil -} diff --git a/libpod/container.go b/libpod/container.go index 558226d63a..9e322160aa 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1458,16 +1458,6 @@ func (c *Container) networks() (map[string]types.PerNetworkOptions, error) { return c.runtime.state.GetNetworks(c) } -// getInterfaceByName returns a formatted interface name for a given -// network along with a bool as to whether the network existed -func (d ContainerNetworkDescriptions) getInterfaceByName(networkName string) (string, bool) { - val, exists := d[networkName] - if !exists { - return "", exists - } - return fmt.Sprintf("eth%d", val), exists -} - // GetNetworkStatus returns the current network status for this container. // This returns a map without deep copying which means this should only ever // be used as read only access, do not modify this status. diff --git a/libpod/container_internal.go b/libpod/container_internal.go index caad86ca6c..31dc7f5f7d 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -709,7 +709,7 @@ func (c *Container) refresh() error { // If a rewrite must happen the config.rewrite field is set to true. if c.config.rewrite { // SafeRewriteContainerConfig must be used with care. Make sure to not change config fields by accident. - if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + if err := c.runtime.state.RewriteContainerConfig(c, c.config); err != nil { return fmt.Errorf("failed to rewrite the config for container %s: %w", c.config.ID, err) } c.config.rewrite = false @@ -2846,7 +2846,7 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error c.config.Spec.Process.Env = envLib.Slice(envMap) } - if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + if err := c.runtime.state.RewriteContainerConfig(c, c.config); err != nil { // Assume DB write failed, revert to old resources block c.config.Spec.Linux.Resources = oldResources c.config.RestartPolicy = oldRestart @@ -2937,7 +2937,7 @@ func (c *Container) updateHealthCheck(newHealthCheckConfig IHealthCheckConfig, c newHealthCheckConfig.SetTo(c.config) - if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + if err := c.runtime.state.RewriteContainerConfig(c, c.config); err != nil { // Assume DB write failed, revert to old resources block oldHealthCheckConfig.SetTo(c.config) return err @@ -2993,7 +2993,7 @@ func (c *Container) updateGlobalHealthCheckConfiguration(globalOptions define.Gl c.config.HealthLogDestination = &dest } - if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + if err := c.runtime.state.RewriteContainerConfig(c, c.config); err != nil { // Assume DB write failed, revert to old resources block c.config.HealthCheckOnFailureAction = oldHealthCheckOnFailureAction c.config.HealthLogDestination = oldHealthLogDestination diff --git a/libpod/info.go b/libpod/info.go index 55f7ba7a10..7e1a8366ce 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -111,7 +111,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { info := define.HostInfo{ Arch: runtime.GOARCH, BuildahVersion: buildah.Version, - DatabaseBackend: r.config.Engine.DBBackend, + DatabaseBackend: r.state.Name(), Linkmode: linkmode.Linkmode(), CPUs: runtime.NumCPU(), CPUUtilization: cpuUtil, diff --git a/libpod/networking_common.go b/libpod/networking_common.go index 57dd956fa2..0ae5069b4f 100644 --- a/libpod/networking_common.go +++ b/libpod/networking_common.go @@ -698,68 +698,3 @@ func (r *Runtime) normalizeNetworkName(nameOrID string) (string, string, error) return net.Name, netIface, nil } - -// ocicniPortsToNetTypesPorts convert the old port format to the new one -// while deduplicating ports into ranges -func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping { - if len(ports) == 0 { - return nil - } - - newPorts := make([]types.PortMapping, 0, len(ports)) - - // first sort the ports - sort.Slice(ports, func(i, j int) bool { - return compareOCICNIPorts(ports[i], ports[j]) - }) - - // we already check if the slice is empty so we can use the first element - currentPort := types.PortMapping{ - HostIP: ports[0].HostIP, - HostPort: uint16(ports[0].HostPort), - ContainerPort: uint16(ports[0].ContainerPort), - Protocol: ports[0].Protocol, - Range: 1, - } - - for i := 1; i < len(ports); i++ { - if ports[i].HostIP == currentPort.HostIP && - ports[i].Protocol == currentPort.Protocol && - ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) && - ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) { - currentPort.Range++ - } else { - newPorts = append(newPorts, currentPort) - currentPort = types.PortMapping{ - HostIP: ports[i].HostIP, - HostPort: uint16(ports[i].HostPort), - ContainerPort: uint16(ports[i].ContainerPort), - Protocol: ports[i].Protocol, - Range: 1, - } - } - } - newPorts = append(newPorts, currentPort) - return newPorts -} - -// compareOCICNIPorts will sort the ocicni ports by -// 1) host ip -// 2) protocol -// 3) hostPort -// 4) container port -func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool { - if i.HostIP != j.HostIP { - return i.HostIP < j.HostIP - } - - if i.Protocol != j.Protocol { - return i.Protocol < j.Protocol - } - - if i.HostPort != j.HostPort { - return i.HostPort < j.HostPort - } - - return i.ContainerPort < j.ContainerPort -} diff --git a/libpod/networking_linux_test.go b/libpod/networking_linux_test.go index 873e1626cb..3bfb05b045 100644 --- a/libpod/networking_linux_test.go +++ b/libpod/networking_linux_test.go @@ -3,242 +3,14 @@ package libpod import ( - "fmt" "net" "reflect" "testing" - "github.com/stretchr/testify/assert" - "github.com/containers/podman/v6/libpod/define" "go.podman.io/common/libnetwork/types" ) -func Test_ocicniPortsToNetTypesPorts(t *testing.T) { - tests := []struct { - name string - arg []types.OCICNIPortMapping - want []types.PortMapping - }{ - { - name: "no ports", - arg: nil, - want: nil, - }, - { - name: "empty ports", - arg: []types.OCICNIPortMapping{}, - want: nil, - }, - { - name: "single port", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 1, - }, - }, - }, - { - name: "two separate ports", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 9000, - ContainerPort: 90, - Protocol: "tcp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 1, - }, - { - HostPort: 9000, - ContainerPort: 90, - Protocol: "tcp", - Range: 1, - }, - }, - }, - { - name: "two ports joined", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "tcp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 2, - }, - }, - }, - { - name: "three ports with different container port are not joined", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 8081, - ContainerPort: 79, - Protocol: "tcp", - }, - { - HostPort: 8082, - ContainerPort: 82, - Protocol: "tcp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 1, - }, - { - HostPort: 8081, - ContainerPort: 79, - Protocol: "tcp", - Range: 1, - }, - { - HostPort: 8082, - ContainerPort: 82, - Protocol: "tcp", - Range: 1, - }, - }, - }, - { - name: "three ports joined (not sorted)", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "tcp", - }, - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 8082, - ContainerPort: 82, - Protocol: "tcp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 3, - }, - }, - }, - { - name: "different protocols ports are not joined", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "udp", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 1, - }, - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "udp", - Range: 1, - }, - }, - }, - { - name: "different host ip ports are not joined", - arg: []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - HostIP: "192.168.1.1", - }, - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "tcp", - HostIP: "192.168.1.2", - }, - }, - want: []types.PortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - Range: 1, - HostIP: "192.168.1.1", - }, - { - HostPort: 8081, - ContainerPort: 81, - Protocol: "tcp", - Range: 1, - HostIP: "192.168.1.2", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := ocicniPortsToNetTypesPorts(tt.arg) - assert.Equal(t, tt.want, result, "ports do not match") - }) - } -} - func Test_resultToBasicNetworkConfig(t *testing.T) { testCases := []struct { description string @@ -440,91 +212,3 @@ func Test_resultToBasicNetworkConfig(t *testing.T) { }) } } - -func benchmarkOCICNIPortsToNetTypesPorts(b *testing.B, ports []types.OCICNIPortMapping) { - for b.Loop() { - ocicniPortsToNetTypesPorts(ports) - } -} - -func Benchmark_ocicniPortsToNetTypesPortsNoPorts(b *testing.B) { - benchmarkOCICNIPortsToNetTypesPorts(b, nil) -} - -func Benchmark_ocicniPortsToNetTypesPorts1(b *testing.B) { - benchmarkOCICNIPortsToNetTypesPorts(b, []types.OCICNIPortMapping{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - }) -} - -func Benchmark_ocicniPortsToNetTypesPorts10(b *testing.B) { - ports := make([]types.OCICNIPortMapping, 0, 10) - for i := int32(8080); i < 8090; i++ { - ports = append(ports, types.OCICNIPortMapping{ - HostPort: i, - ContainerPort: i, - Protocol: "tcp", - }) - } - b.ResetTimer() - benchmarkOCICNIPortsToNetTypesPorts(b, ports) -} - -func Benchmark_ocicniPortsToNetTypesPorts100(b *testing.B) { - ports := make([]types.OCICNIPortMapping, 0, 100) - for i := int32(8080); i < 8180; i++ { - ports = append(ports, types.OCICNIPortMapping{ - HostPort: i, - ContainerPort: i, - Protocol: "tcp", - }) - } - b.ResetTimer() - benchmarkOCICNIPortsToNetTypesPorts(b, ports) -} - -func Benchmark_ocicniPortsToNetTypesPorts1k(b *testing.B) { - ports := make([]types.OCICNIPortMapping, 0, 1000) - for i := int32(8080); i < 9080; i++ { - ports = append(ports, types.OCICNIPortMapping{ - HostPort: i, - ContainerPort: i, - Protocol: "tcp", - }) - } - b.ResetTimer() - benchmarkOCICNIPortsToNetTypesPorts(b, ports) -} - -func Benchmark_ocicniPortsToNetTypesPorts10k(b *testing.B) { - ports := make([]types.OCICNIPortMapping, 0, 30000) - for i := int32(8080); i < 18080; i++ { - ports = append(ports, types.OCICNIPortMapping{ - HostPort: i, - ContainerPort: i, - Protocol: "tcp", - }) - } - b.ResetTimer() - benchmarkOCICNIPortsToNetTypesPorts(b, ports) -} - -func Benchmark_ocicniPortsToNetTypesPorts1m(b *testing.B) { - ports := make([]types.OCICNIPortMapping, 0, 1000000) - for j := range 20 { - for i := int32(1); i <= 50000; i++ { - ports = append(ports, types.OCICNIPortMapping{ - HostPort: i, - ContainerPort: i, - Protocol: "tcp", - HostIP: fmt.Sprintf("192.168.1.%d", j), - }) - } - } - b.ResetTimer() - benchmarkOCICNIPortsToNetTypesPorts(b, ports) -} diff --git a/libpod/runtime.go b/libpod/runtime.go index f74d13264e..ffc6aed383 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "io/fs" "os" "path/filepath" "slices" @@ -295,31 +294,11 @@ func getDBState(runtime *Runtime) (State, error) { return nil, err } - // get default boltdb path - baseDir := runtime.config.Engine.StaticDir - if runtime.storageConfig.TransientStore { - baseDir = runtime.config.Engine.TmpDir - } - boltDBPath := filepath.Join(baseDir, "bolt_state.db") - switch backend { - case config.DBBackendDefault: - // for backwards compatibility check if boltdb exists, if it does not we use sqlite - if err := fileutils.Exists(boltDBPath); err != nil { - if errors.Is(err, fs.ErrNotExist) { - // need to set DBBackend string so podman info will show the backend name correctly - runtime.config.Engine.DBBackend = config.DBBackendSQLite.String() - return NewSqliteState(runtime) - } - // Return error here some other problem with the boltdb file, rather than silently - // switch to sqlite which would be hard to debug for the user return the error back - // as this likely a real bug. - return nil, err - } - runtime.config.Engine.DBBackend = config.DBBackendBoltDB.String() - fallthrough case config.DBBackendBoltDB: - return NewBoltState(boltDBPath, runtime) + return nil, fmt.Errorf("the BoltDB database backend was removed in Podman 6.0") + case config.DBBackendDefault: + fallthrough case config.DBBackendSQLite: return NewSqliteState(runtime) default: diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 2ec90c4499..fd84be3976 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -146,7 +146,7 @@ func (r *Runtime) RenameContainer(_ context.Context, ctr *Container, newName str ctr.config.Name = newName // Step 2: rewrite the old container's config in the DB. - if err := r.state.SafeRewriteContainerConfig(ctr, oldName, ctr.config.Name, ctr.config); err != nil { + if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil { // Assume the rename failed. // Set config back to the old name so reflect what is actually // present in the DB. @@ -594,11 +594,8 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai // being removed pod.lock.Lock() defer pod.lock.Unlock() - - if err := r.state.AddContainerToPod(pod, ctr); err != nil { - return nil, err - } - } else if err := r.state.AddContainer(ctr); err != nil { + } + if err := r.state.AddContainer(ctr); err != nil { return nil, err } @@ -1004,17 +1001,10 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, opts ctrRmO } // Remove the container from the state - if c.config.Pod != "" { - // If we're removing the pod, the container will be evicted - // from the state elsewhere - if err := r.state.RemoveContainerFromPod(pod, c); err != nil { - reportErrorf("removing container %s from database: %w", c.ID(), err) - } - } else { - if err := r.state.RemoveContainer(c); err != nil { - reportErrorf("removing container %s from database: %w", c.ID(), err) - } + if err := r.state.RemoveContainer(c); err != nil { + reportErrorf("removing container %s from database: %w", c.ID(), err) } + removedCtrs[c.ID()] = nil // Remove the container's CID file on container removal. @@ -1174,16 +1164,8 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol var cleanupErr error // Remove the container from the state - if c.config.Pod != "" { - // If we're removing the pod, the container will be evicted - // from the state elsewhere - if err := r.state.RemoveContainerFromPod(pod, c); err != nil { - cleanupErr = err - } - } else { - if err := r.state.RemoveContainer(c); err != nil { - cleanupErr = err - } + if err := r.state.RemoveContainer(c); err != nil { + cleanupErr = err } // Unmount container mount points diff --git a/libpod/runtime_test.go b/libpod/runtime_test.go index 0ed053a521..f1f486ced4 100644 --- a/libpod/runtime_test.go +++ b/libpod/runtime_test.go @@ -10,7 +10,7 @@ import ( ) func Test_generateName(t *testing.T) { - state, path, _, err := getEmptyBoltState() + state, path, _, err := getEmptySqliteState() assert.NoError(t, err) defer os.RemoveAll(path) defer state.Close() diff --git a/libpod/sqlite_state.go b/libpod/sqlite_state.go index ba044f560f..f688b7c9b9 100644 --- a/libpod/sqlite_state.go +++ b/libpod/sqlite_state.go @@ -15,6 +15,7 @@ import ( "github.com/containers/podman/v6/libpod/define" "github.com/sirupsen/logrus" "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" "go.podman.io/storage" // SQLite backend for database/sql @@ -104,6 +105,11 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) { return state, nil } +// Name gets the name of the current DB backend. +func (s *SQLiteState) Name() string { + return config.DBBackendSQLite.String() +} + // Close closes the state and prevents further use func (s *SQLiteState) Close() error { if err := s.conn.Close(); err != nil { @@ -661,17 +667,7 @@ func (s *SQLiteState) HasContainer(id string) (bool, error) { row := s.conn.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=?;", id) - var check int - if err := row.Scan(&check); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return false, nil - } - return false, fmt.Errorf("looking up container %s in database: %w", id, err) - } else if check != 1 { - return false, fmt.Errorf("check digit for container %s lookup incorrect: %w", id, define.ErrInternal) - } - - return true, nil + return hasContainerBody(id, row) } // AddContainer adds a container to the state @@ -685,10 +681,6 @@ func (s *SQLiteState) AddContainer(ctr *Container) error { return define.ErrCtrRemoved } - if ctr.config.Pod != "" { - return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg) - } - return s.addContainer(ctr) } @@ -700,10 +692,6 @@ func (s *SQLiteState) RemoveContainer(ctr *Container) error { return define.ErrDBClosed } - if ctr.config.Pod != "" { - return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists) - } - return s.removeContainer(ctr) } @@ -788,7 +776,7 @@ func (s *SQLiteState) SaveContainer(ctr *Container) (defErr error) { // 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 *SQLiteState) ContainerInUse(ctr *Container) ([]string, error) { +func (s *SQLiteState) ContainerInUse(ctr *Container) (_ []string, defErr error) { if !s.valid { return nil, define.ErrDBClosed } @@ -797,7 +785,30 @@ func (s *SQLiteState) ContainerInUse(ctr *Container) ([]string, error) { return nil, define.ErrCtrRemoved } - rows, err := s.conn.Query("SELECT ID FROM ContainerDependency WHERE DependencyID=?;", ctr.ID()) + tx, err := s.conn.Begin() + if err != nil { + return nil, fmt.Errorf("beginning transaction to retrieve container dependents: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction retrieve container dependents: %v", err) + } + } + }() + + row := tx.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=?;", ctr.ID()) + + exists, err := hasContainerBody(ctr.ID(), row) + if err != nil { + return nil, err + } + if !exists { + ctr.valid = false + return nil, define.ErrNoSuchCtr + } + + rows, err := tx.Query("SELECT ID FROM ContainerDependency WHERE DependencyID=?;", ctr.ID()) if err != nil { return nil, fmt.Errorf("retrieving containers that depend on container %s: %w", ctr.ID(), err) } @@ -815,6 +826,10 @@ func (s *SQLiteState) ContainerInUse(ctr *Container) ([]string, error) { return nil, err } + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("committing transaction to retrieve container %s dependents: %w", ctr.ID(), err) + } + return deps, nil } @@ -1230,7 +1245,6 @@ func (s *SQLiteState) RemoveContainerExecSessions(ctr *Container) (defErr error) // container ID. // WARNING: This function is DANGEROUS. Do not use without reading the full // comment on this function in state.go. -// TODO: Once BoltDB is removed, this can be combined with SafeRewriteContainerConfig. func (s *SQLiteState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error { if !s.valid { return define.ErrDBClosed @@ -1243,31 +1257,6 @@ func (s *SQLiteState) RewriteContainerConfig(ctr *Container, newCfg *ContainerCo return s.rewriteContainerConfig(ctr, newCfg) } -// SafeRewriteContainerConfig rewrites a container's configuration in a more -// limited fashion than RewriteContainerConfig. It is marked as safe to use -// under most circumstances, unlike RewriteContainerConfig. -// DO NOT USE TO: Change container dependencies, change pod membership, change -// locks, change container ID. -// TODO: Once BoltDB is removed, this can be combined with RewriteContainerConfig. -func (s *SQLiteState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { - if !s.valid { - return define.ErrDBClosed - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if newName != "" && newCfg.Name != newName { - 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 fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg) - } - - return s.rewriteContainerConfig(ctr, newCfg) -} - // RewritePodConfig rewrites a pod's configuration. // WARNING: This function is DANGEROUS. Do not use without reading the full // comment on this function in state.go. @@ -1450,17 +1439,7 @@ func (s *SQLiteState) HasPod(id string) (bool, error) { row := s.conn.QueryRow("SELECT 1 FROM PodConfig WHERE ID=?;", id) - var check int - if err := row.Scan(&check); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return false, nil - } - return false, fmt.Errorf("looking up pod %s in database: %w", id, err) - } else if check != 1 { - return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal) - } - - return true, nil + return hasPodBody(id, row) } // PodHasContainer checks if the given pod has a container with the given ID @@ -1501,6 +1480,15 @@ func (s *SQLiteState) PodContainersByID(pod *Pod) ([]string, error) { return nil, define.ErrPodRemoved } + hasPod, err := s.HasPod(pod.ID()) + if err != nil { + return nil, err + } + if !hasPod { + pod.valid = false + return nil, define.ErrNoSuchPod + } + rows, err := s.conn.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID()) if err != nil { return nil, fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err) @@ -1533,6 +1521,15 @@ func (s *SQLiteState) PodContainers(pod *Pod) ([]*Container, error) { return nil, define.ErrPodRemoved } + hasPod, err := s.HasPod(pod.ID()) + if err != nil { + return nil, err + } + if !hasPod { + pod.valid = false + return nil, define.ErrNoSuchPod + } + rows, err := s.conn.Query("SELECT JSON FROM ContainerConfig WHERE PodID=?;", pod.ID()) if err != nil { return nil, fmt.Errorf("retrieving containers of pod %s from database: %w", pod.ID(), err) @@ -1737,25 +1734,63 @@ func (s *SQLiteState) RemovePodContainers(pod *Pod) (defErr error) { } }() + hasPod, err := hasPodTx(pod.ID(), tx) + if err != nil { + return err + } + if !hasPod { + pod.valid = false + return define.ErrNoSuchPod + } + rows, err := tx.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID()) if err != nil { return fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err) } defer rows.Close() + var ids []string for rows.Next() { var id string if err := rows.Scan(&id); err != nil { return fmt.Errorf("scanning container from database: %w", err) } - - if err := s.removeContainerWithTx(id, tx); err != nil { - return err - } + ids = append(ids, id) } if err := rows.Err(); err != nil { return err } + if len(ids) == 0 { + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing pod containers %s removal transaction: %w", pod.ID(), err) + } + + return nil + } + + // This is absolutely cursed, but still seems to be the best way to pass an array as a SQLite argument + jsonIDs, err := json.Marshal(ids) + if err != nil { + return err + } + if _, err := tx.Exec("DELETE FROM ContainerState WHERE ID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container states: %w", pod.ID(), err) + } + if _, err := tx.Exec("DELETE FROM ContainerDependency WHERE ID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container dependencies: %w", pod.ID(), err) + } + if _, err := tx.Exec("DELETE FROM ContainerVolume WHERE ContainerID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container volumes: %w", pod.ID(), err) + } + if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container exec sessions: %w", pod.ID(), err) + } + if _, err := tx.Exec("DELETE FROM ContainerConfig WHERE ID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container configs: %w", pod.ID(), err) + } + if _, err := tx.Exec("DELETE FROM IDNamespace WHERE ID in (SELECT value from json_each(?));", jsonIDs); err != nil { + return fmt.Errorf("removing pod %s container IDs: %w", pod.ID(), err) + } if err := tx.Commit(); err != nil { return fmt.Errorf("committing pod containers %s removal transaction: %w", pod.ID(), err) @@ -1764,50 +1799,6 @@ func (s *SQLiteState) RemovePodContainers(pod *Pod) (defErr error) { return nil } -// AddContainerToPod adds the given container to an existing pod -// The container will be added to the state and the pod -func (s *SQLiteState) AddContainerToPod(pod *Pod, ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - if !ctr.valid { - return define.ErrCtrRemoved - } - - if ctr.config.Pod != pod.ID() { - return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) - } - - return s.addContainer(ctr) -} - -// RemoveContainerFromPod removes a container from an existing pod -// The container will also be removed from the state -func (s *SQLiteState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { - if !s.valid { - return define.ErrDBClosed - } - - if !pod.valid { - return define.ErrPodRemoved - } - - if ctr.config.Pod == "" { - 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 fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg) - } - - return s.removeContainer(ctr) -} - // UpdatePod updates a pod's state from the database. func (s *SQLiteState) UpdatePod(pod *Pod) error { if !s.valid { diff --git a/libpod/sqlite_state_internal.go b/libpod/sqlite_state_internal.go index ae583a286c..8e45662053 100644 --- a/libpod/sqlite_state_internal.go +++ b/libpod/sqlite_state_internal.go @@ -491,6 +491,9 @@ func (s *SQLiteState) removeContainer(ctr *Container) (defErr error) { }() if err := s.removeContainerWithTx(ctr.ID(), tx); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + ctr.valid = false + } return err } @@ -504,28 +507,51 @@ func (s *SQLiteState) removeContainer(ctr *Container) (defErr error) { // removeContainerWithTx removes the container with the specified transaction. // Callers are responsible for committing. func (s *SQLiteState) removeContainerWithTx(id string, tx *sql.Tx) error { - // TODO TODO TODO: - // Need to verify that at least 1 row was deleted from ContainerConfig. - // Otherwise return ErrNoSuchCtr. - if _, err := tx.Exec("DELETE FROM IDNamespace WHERE ID=?;", id); err != nil { - return fmt.Errorf("removing container %s id from database: %w", id, err) + var lastErr error + + exec := func(countRows bool, field, query string, args ...any) { + res, err := tx.Exec(query, args...) + if err != nil { + if lastErr != nil { + logrus.Errorf("Error: %v", lastErr) + } + lastErr = fmt.Errorf("removing container %s %s from database: %w", id, field, err) + return + } + if !countRows { + return + } + n, err := res.RowsAffected() + if err != nil { + if lastErr != nil { + logrus.Errorf("Error: %v", lastErr) + } + lastErr = fmt.Errorf("getting rows affected while removing container %s %s from database: %w", id, field, err) + } + if n == 0 { + if lastErr != nil { + // No need to report duplicate ErrNoSuchCtr + if errors.Is(lastErr, define.ErrNoSuchCtr) { + return + } + logrus.Errorf("Error: %v", lastErr) + } + lastErr = fmt.Errorf("removing container %s from database: %w", id, define.ErrNoSuchCtr) + } else if n > 1 { + if lastErr != nil { + logrus.Errorf("Error: %v", lastErr) + } + lastErr = fmt.Errorf("removing container %s %s from database found more than 1 row: %w", id, field, define.ErrInternal) + } } - if _, err := tx.Exec("DELETE FROM ContainerConfig WHERE ID=?;", id); err != nil { - return fmt.Errorf("removing container %s config from database: %w", id, err) - } - if _, err := tx.Exec("DELETE FROM ContainerState WHERE ID=?;", id); err != nil { - return fmt.Errorf("removing container %s state from database: %w", id, err) - } - if _, err := tx.Exec("DELETE FROM ContainerDependency WHERE ID=?;", id); err != nil { - return fmt.Errorf("removing container %s dependencies from database: %w", id, err) - } - if _, err := tx.Exec("DELETE FROM ContainerVolume WHERE ContainerID=?;", id); err != nil { - return fmt.Errorf("removing container %s volumes from database: %w", id, err) - } - if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID=?;", id); err != nil { - return fmt.Errorf("removing container %s exec sessions from database: %w", id, err) - } - return nil + + exec(true, "id", "DELETE FROM IDNamespace WHERE ID=?;", id) + exec(true, "config", "DELETE FROM ContainerConfig WHERE ID=?;", id) + exec(true, "state", "DELETE FROM ContainerState WHERE ID=?;", id) + exec(false, "dependencies", "DELETE FROM ContainerDependency WHERE ID=?;", id) + exec(false, "volumes", "DELETE FROM ContainerVolume WHERE ContainerID=?;", id) + exec(false, "exec sessions", "DELETE FROM ContainerExecSession WHERE ContainerID=?;", id) + return lastErr } // networkModify allows you to modify or add a new network, to add a new network use the new bool @@ -578,3 +604,36 @@ func (s *SQLiteState) networkModify(ctr *Container, network string, opts types.P return nil } + +func hasContainerBody(id string, row *sql.Row) (bool, error) { + var check int + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("looking up container %s in database: %w", id, err) + } else if check != 1 { + return false, fmt.Errorf("check digit for container %s lookup incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} + +func hasPodTx(id string, tx *sql.Tx) (bool, error) { + row := tx.QueryRow("SELECT 1 FROM PodConfig WHERE ID=?;", id) + return hasPodBody(id, row) +} + +func hasPodBody(id string, row *sql.Row) (bool, error) { + var check int + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("looking up pod %s in database: %w", id, err) + } else if check != 1 { + return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} diff --git a/libpod/state.go b/libpod/state.go index a07ee92a6c..05b904da8d 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -25,6 +25,9 @@ type State interface { //nolint:interfacebloat // Refresh clears container and pod states after a reboot Refresh() error + // Name() returns the name of the current state. + Name() string + // GetDBConfig retrieves several paths configured within the database // when it was created - namely, Libpod root and tmp dirs, c/storage // root and tmp dirs, and c/storage graph driver. @@ -47,40 +50,26 @@ type State interface { //nolint:interfacebloat GetPodName(id string) (string, error) // Return a container from the database from its full ID. - // If the container is not in the set namespace, an error will be - // returned. Container(id string) (*Container, error) // Return a container ID from the database by full or partial ID or full // name. LookupContainerID(idOrName string) (string, error) // Return a container from the database by full or partial ID or full // name. - // Containers not in the set namespace will be ignored. LookupContainer(idOrName string) (*Container, error) // Check if a container with the given full ID exists in the database. - // If the container exists but is not in the set namespace, false will - // be returned. HasContainer(id string) (bool, error) // Adds container to state. - // The container cannot be part of a pod. - // The container must have globally unique name and ID - pod names and - // IDs also conflict with container names and IDs. - // The container must be in the set namespace if a namespace has been - // set. - // All containers this container depends on must be part of the same - // namespace and must not be joined to a pod. + // The container must have globally unique ID - pod IDs also conflict + // with container names and IDs. AddContainer(ctr *Container) error // Removes container from state. - // Containers that are part of pods must use RemoveContainerFromPod. - // The container must be part of the set namespace. - // All dependencies must be removed first. + // All containers depending on this container must be removed first. // All exec sessions referencing the container must be removed first. RemoveContainer(ctr *Container) error // UpdateContainer updates a container's state from the backing store. - // The container must be part of the set namespace. UpdateContainer(ctr *Container) error // SaveContainer saves a container's current state to the backing store. - // The container must be part of the set namespace. SaveContainer(ctr *Container) error // ContainerInUse checks if other containers depend upon a given // container. @@ -88,12 +77,9 @@ type State interface { //nolint:interfacebloat // container. If the slice is empty, no container depend on the given // container. // A container cannot be removed if other containers depend on it. - // The container being checked must be part of the set namespace. ContainerInUse(ctr *Container) ([]string, error) // Retrieves all containers presently in state. // If `loadState` is set, the containers' state will be loaded as well. - // If a namespace is set, only containers within the namespace will be - // returned. AllContainers(loadState bool) ([]*Container, error) // Get networks the container is currently connected to. @@ -151,35 +137,17 @@ type State interface { //nolint:interfacebloat // PLEASE READ FULL DESCRIPTION BEFORE USING. // Rewrite a container's configuration. // This function breaks libpod's normal prohibition on a read-only - // configuration, and as such should be used EXTREMELY SPARINGLY and - // only in very specific circumstances. - // Specifically, it is ONLY safe to use thing function to make changes - // that result in a functionally identical configuration (migrating to - // newer, but identical, configuration fields), or during libpod init - // WHILE HOLDING THE ALIVE LOCK (to prevent other libpod instances from - // being initialized). - // Most things in config can be changed by this, but container ID and - // name ABSOLUTELY CANNOT BE ALTERED. If you do so, there is a high - // potential for database corruption. - // There are a lot of capital letters and conditions here, but the short - // answer is this: use this only very sparingly, and only if you really - // know what you're doing. - // TODO: Once BoltDB is removed, RewriteContainerConfig and - // SafeRewriteContainerConfig can be merged. + // configuration, and as such should be used sparingly. + // Other running Libpod instances generally WILL NOT pick up changes + // until they are restarted - meaning we can have two Libpod instances + // running concurrently, which have different configs for the same + // container. This is not a good thing, and unavoidable given the + // fundamental architecture of Podman - which are all good reasons to + // not use this unless absolutely necessary. + // Container ID ABSOLUTELY CANNOT BE ALTERED. + // Container dependencies and pod membership ABSOLUTELY CANNOT BE + // ALTERED. RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error - // This is a more limited version of RewriteContainerConfig, though it - // comes with the added ability to alter a container's name. In exchange - // it loses the ability to manipulate the container's locks. - // It is not intended to be as restrictive as RewriteContainerConfig, in - // that we allow it to be run while other Podman processes are running, - // and without holding the alive lock. - // Container ID and pod membership still *ABSOLUTELY CANNOT* be altered. - // Also, you cannot change a container's dependencies - shared namespace - // containers or generic dependencies - at present. This is - // theoretically possible but not yet implemented. - // If newName is not "" the container will be renamed to the new name. - // The oldName parameter is only required if newName is given. - SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error // PLEASE READ THE DESCRIPTION FOR RewriteContainerConfig BEFORE USING. // This function is identical to RewriteContainerConfig, save for the // fact that it is used with pods instead. @@ -196,60 +164,35 @@ type State interface { //nolint:interfacebloat RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) error // Accepts full ID of pod. - // If the pod given is not in the set namespace, an error will be - // returned. Pod(id string) (*Pod, error) // Accepts full or partial IDs (as long as they are unique) and names. - // Pods not in the set namespace are ignored. LookupPod(idOrName string) (*Pod, error) // Checks if a pod with the given ID is present in the state. - // If the given pod is not in the set namespace, false is returned. HasPod(id string) (bool, error) // Check if a pod has a container with the given ID. - // The pod must be part of the set namespace. PodHasContainer(pod *Pod, ctrID string) (bool, error) // Get the IDs of all containers in a pod. - // The pod must be part of the set namespace. PodContainersByID(pod *Pod) ([]string, error) // Get all the containers in a pod. - // The pod must be part of the set namespace. PodContainers(pod *Pod) ([]*Container, error) // Adds pod to state. - // The pod must be part of the set namespace. - // The pod's name and ID must be globally unique. + // The pod's name must not be shared by any other pods. + // The pod's ID must be globally unique - not shared with either + // containers or pods. AddPod(pod *Pod) error // Removes pod from state. // Only empty pods can be removed from the state. - // The pod must be part of the set namespace. RemovePod(pod *Pod) error // Remove all containers from a pod. // Used to simultaneously remove containers that might otherwise have // dependency issues. // Will fail if a dependency outside the pod is encountered. - // The pod must be part of the set namespace. RemovePodContainers(pod *Pod) error - // AddContainerToPod adds a container to an existing pod. - // The container given will be added to the state and the pod. - // The container and its dependencies must be part of the given pod, - // and the given pod's namespace. - // The pod must be part of the set namespace. - // The pod must already exist in the state. - // The container's name and ID must be globally unique. - AddContainerToPod(pod *Pod, ctr *Container) error - // RemoveContainerFromPod removes a container from an existing pod. - // The container will also be removed from the state. - // The container must be in the given pod, and the pod must be in the - // set namespace. - RemoveContainerFromPod(pod *Pod, ctr *Container) error // UpdatePod updates a pod's state from the database. - // The pod must be in the set namespace. UpdatePod(pod *Pod) error // SavePod saves a pod's state to the database. - // The pod must be in the set namespace. SavePod(pod *Pod) error // Retrieves all pods presently in state. - // If a namespace has been set, only pods in that namespace will be - // returned. AllPods() ([]*Pod, error) // Volume accepts full name of volume diff --git a/libpod/state_test.go b/libpod/state_test.go index 5522c7a6c8..7eb6e092ef 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -4,7 +4,6 @@ package libpod import ( "os" - "path/filepath" "strings" "testing" "time" @@ -13,7 +12,6 @@ import ( "github.com/containers/podman/v6/libpod/lock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.podman.io/common/libnetwork/types" "go.podman.io/common/pkg/config" "go.podman.io/storage" ) @@ -30,12 +28,11 @@ const ( var ( testedStates = map[string]emptyStateFunc{ - "boltdb": getEmptyBoltState, + "sqlite": getEmptySqliteState, } ) -// Get an empty BoltDB state for use in tests -func getEmptyBoltState() (_ State, _ string, _ lock.Manager, retErr error) { +func getEmptySqliteState() (_ State, _ string, _ lock.Manager, retErr error) { tmpDir, err := os.MkdirTemp("", tmpDirPrefix) if err != nil { return nil, "", nil, err @@ -46,12 +43,6 @@ func getEmptyBoltState() (_ State, _ string, _ lock.Manager, retErr error) { } }() - if err := os.Setenv("CI_DESIRED_DATABASE", "boltdb"); err != nil { - return nil, "", nil, err - } - - dbPath := filepath.Join(tmpDir, "db.sql") - lockManager, err := lock.NewInMemoryManager(16) if err != nil { return nil, "", nil, err @@ -60,9 +51,13 @@ func getEmptyBoltState() (_ State, _ string, _ lock.Manager, retErr error) { runtime := new(Runtime) runtime.config = new(config.Config) runtime.storageConfig = storage.StoreOptions{} + runtime.storageSet = storageSet{} runtime.lockManager = lockManager - state, err := NewBoltState(dbPath, runtime) + runtime.storageConfig.GraphRoot = tmpDir + runtime.storageSet.StaticDirSet = true + + state, err := NewSqliteState(runtime) if err != nil { return nil, "", nil, err } @@ -200,47 +195,6 @@ func TestAddCtrPodDupIDFails(t *testing.T) { }) } -func TestAddCtrPodDupNameFails(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testPod, err := getTestPod1(manager) - assert.NoError(t, err) - testCtr, err := getTestContainer(strings.Repeat("2", 32), testPod.Name(), manager) - assert.NoError(t, err) - - err = state.AddPod(testPod) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.Error(t, err) - - ctrs, err := state.AllContainers(false) - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs)) - }) -} - -func TestAddCtrInPodFails(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testPod, err := getTestPod1(manager) - assert.NoError(t, err) - - testCtr, err := getTestCtr2(manager) - assert.NoError(t, err) - - testCtr.config.Pod = testPod.ID() - - err = state.AddPod(testPod) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.Error(t, err) - - ctrs, err := state.AllContainers(false) - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs)) - }) -} - func TestAddCtrDepInPodFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testPod, err := getTestPod1(manager) @@ -259,7 +213,7 @@ func TestAddCtrDepInPodFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) err = state.AddContainer(testCtr2) @@ -546,7 +500,6 @@ func TestRemoveNonexistentContainerFails(t *testing.T) { err = state.RemoveContainer(testCtr) assert.Error(t, err) - assert.False(t, testCtr.valid) }) } @@ -883,20 +836,6 @@ func TestCannotUsePodAsDependency(t *testing.T) { }) } -func TestAddContainerEmptyNetworkNameErrors(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testCtr, err := getTestCtr1(manager) - assert.NoError(t, err) - - testCtr.config.Networks = map[string]types.PerNetworkOptions{ - "": {}, - } - - err = state.AddContainer(testCtr) - assert.Error(t, err) - }) -} - func TestCannotUseBadIDAsDependency(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testCtr, err := getTestCtr1(manager) @@ -1347,26 +1286,6 @@ func TestAddPodCtrIDConflictFails(t *testing.T) { }) } -func TestAddPodCtrNameConflictFails(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testCtr, err := getTestCtr1(manager) - assert.NoError(t, err) - - testPod, err := getTestPod(strings.Repeat("3", 32), testCtr.Name(), manager) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - err = state.AddPod(testPod) - assert.Error(t, err) - - allPods, err := state.AllPods() - assert.NoError(t, err) - assert.Equal(t, 0, len(allPods)) - }) -} - func TestRemovePodInvalidPodErrors(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, _ lock.Manager) { err := state.RemovePod(&Pod{config: &PodConfig{}}) @@ -1439,7 +1358,7 @@ func TestRemovePodNotEmptyFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) err = state.RemovePod(testPod) @@ -1463,10 +1382,10 @@ func TestRemovePodAfterEmptySucceeds(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr) + err = state.RemoveContainer(testCtr) assert.NoError(t, err) err = state.RemovePod(testPod) @@ -1607,7 +1526,7 @@ func TestPodHasContainerSucceeds(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) exist, err := state.PodHasContainer(testPod, testCtr.ID()) @@ -1630,7 +1549,6 @@ func TestPodContainerdByIDPodNotInState(t *testing.T) { _, err = state.PodContainersByID(testPod) assert.Error(t, err) - assert.False(t, testPod.valid) }) } @@ -1661,7 +1579,7 @@ func TestPodContainersByIDOneContainer(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) ctrs, err := state.PodContainersByID(testPod) @@ -1695,21 +1613,21 @@ func TestPodContainersByIDMultipleContainers(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, len(ctrs0)) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) ctrs1, err := state.PodContainersByID(testPod) assert.NoError(t, err) assert.Equal(t, 1, len(ctrs1)) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) ctrs2, err := state.PodContainersByID(testPod) assert.NoError(t, err) assert.Equal(t, 2, len(ctrs2)) - err = state.AddContainerToPod(testPod, testCtr3) + err = state.AddContainer(testCtr3) assert.NoError(t, err) ctrs3, err := state.PodContainersByID(testPod) @@ -1732,7 +1650,6 @@ func TestPodContainersPodNotInState(t *testing.T) { _, err = state.PodContainers(testPod) assert.Error(t, err) - assert.False(t, testPod.valid) }) } @@ -1763,7 +1680,7 @@ func TestPodContainersOneContainer(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) ctrs, err := state.PodContainers(testPod) @@ -1798,21 +1715,21 @@ func TestPodContainersMultipleContainers(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, len(ctrs0)) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) ctrs1, err := state.PodContainers(testPod) assert.NoError(t, err) assert.Equal(t, 1, len(ctrs1)) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) ctrs2, err := state.PodContainers(testPod) assert.NoError(t, err) assert.Equal(t, 2, len(ctrs2)) - err = state.AddContainerToPod(testPod, testCtr3) + err = state.AddContainer(testCtr3) assert.NoError(t, err) ctrs3, err := state.PodContainers(testPod) @@ -1868,7 +1785,7 @@ func TestRemovePodContainersOneContainer(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) err = state.RemovePodContainers(testPod) @@ -1895,7 +1812,7 @@ func TestRemovePodContainersPreservesCtrOutsidePod(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) err = state.AddContainer(testCtr2) @@ -1930,10 +1847,10 @@ func TestRemovePodContainersTwoContainers(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) err = state.RemovePodContainers(testPod) @@ -1962,10 +1879,10 @@ func TestRemovePodContainerDependencyInPod(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) err = state.RemovePodContainers(testPod) @@ -1981,8 +1898,9 @@ func TestAddContainerToPodInvalidPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testCtr, err := getTestCtr1(manager) assert.NoError(t, err) + testCtr.config.Pod = "1" - err = state.AddContainerToPod(&Pod{config: &PodConfig{}}, testCtr) + err = state.AddContainer(testCtr) assert.Error(t, err) }) } @@ -1995,7 +1913,7 @@ func TestAddContainerToPodInvalidCtr(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, &Container{config: &ContainerConfig{ID: "1234"}}) + err = state.AddContainer(&Container{config: &ContainerConfig{ID: "1234"}}) assert.Error(t, err) ctrs, err := state.PodContainersByID(testPod) @@ -2013,9 +1931,8 @@ func TestAddContainerToPodPodNotInState(t *testing.T) { assert.NoError(t, err) testCtr.config.Pod = testPod.ID() - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.Error(t, err) - assert.False(t, testPod.valid) }) } @@ -2031,7 +1948,7 @@ func TestAddContainerToPodSucceeds(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) ctrs, err := state.PodContainers(testPod) @@ -2063,10 +1980,10 @@ func TestAddContainerToPodTwoContainers(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) ctrs, err := state.PodContainers(testPod) @@ -2094,7 +2011,7 @@ func TestAddContainerToPodWithAddContainer(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) err = state.AddContainer(testCtr2) @@ -2130,7 +2047,7 @@ func TestAddContainerToPodCtrIDConflict(t *testing.T) { err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2161,7 +2078,7 @@ func TestAddContainerToPodCtrNameConflict(t *testing.T) { err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2186,32 +2103,7 @@ func TestAddContainerToPodPodIDConflict(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) - assert.Error(t, err) - - ctrs, err := state.PodContainers(testPod) - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs)) - - allCtrs, err := state.AllContainers(false) - assert.NoError(t, err) - assert.Equal(t, 0, len(allCtrs)) - }) -} - -func TestAddContainerToPodPodNameConflict(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testPod, err := getTestPod1(manager) - assert.NoError(t, err) - - testCtr, err := getTestContainer(strings.Repeat("2", 32), testPod.Name(), manager) - assert.NoError(t, err) - testCtr.config.Pod = testPod.ID() - - err = state.AddPod(testPod) - assert.NoError(t, err) - - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2241,10 +2133,10 @@ func TestAddContainerToPodAddsDependencies(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) deps, err := state.ContainerInUse(testCtr1) @@ -2267,7 +2159,7 @@ func TestAddContainerToPodPodDependencyFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2289,7 +2181,7 @@ func TestAddContainerToPodBadDependencyFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2317,7 +2209,7 @@ func TestAddContainerToPodDependencyOutsidePodFails(t *testing.T) { err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2338,8 +2230,9 @@ func TestRemoveContainerFromPodBadPodFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testCtr, err := getTestCtr1(manager) assert.NoError(t, err) + testCtr.config.Pod = "1" - err = state.RemoveContainerFromPod(&Pod{config: &PodConfig{}}, testCtr) + err = state.RemoveContainer(testCtr) assert.Error(t, err) }) } @@ -2353,10 +2246,8 @@ func TestRemoveContainerFromPodPodNotInStateFails(t *testing.T) { assert.NoError(t, err) testCtr.config.Pod = testPod.ID() - err = state.RemoveContainerFromPod(testPod, testCtr) + err = state.RemoveContainer(testCtr) assert.Error(t, err) - - assert.False(t, testPod.valid) }) } @@ -2372,38 +2263,13 @@ func TestRemoveContainerFromPodCtrNotInStateFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr) + err = state.RemoveContainer(testCtr) assert.Error(t, err) assert.False(t, testCtr.valid) }) } -func TestRemoveContainerFromPodCtrNotInPodFails(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testPod, err := getTestPod1(manager) - assert.NoError(t, err) - - testCtr, err := getTestCtr2(manager) - assert.NoError(t, err) - - err = state.AddPod(testPod) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - err = state.RemoveContainerFromPod(testPod, testCtr) - assert.Error(t, err) - - assert.True(t, testCtr.valid) - - ctrs, err := state.AllContainers(false) - assert.NoError(t, err) - assert.Equal(t, 1, len(ctrs)) - }) -} - func TestRemoveContainerFromPodSucceeds(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testPod, err := getTestPod1(manager) @@ -2416,10 +2282,10 @@ func TestRemoveContainerFromPodSucceeds(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr) + err = state.AddContainer(testCtr) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr) + err = state.RemoveContainer(testCtr) assert.NoError(t, err) ctrs, err := state.PodContainers(testPod) @@ -2449,13 +2315,13 @@ func TestRemoveContainerFromPodWithDependencyFails(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr1) + err = state.RemoveContainer(testCtr1) assert.Error(t, err) ctrs, err := state.PodContainers(testPod) @@ -2485,16 +2351,16 @@ func TestRemoveContainerFromPodWithDependencySucceedsAfterDepRemoved(t *testing. err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr1) + err = state.AddContainer(testCtr1) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, testCtr2) + err = state.AddContainer(testCtr2) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr2) + err = state.RemoveContainer(testCtr2) assert.NoError(t, err) - err = state.RemoveContainerFromPod(testPod, testCtr1) + err = state.RemoveContainer(testCtr1) assert.NoError(t, err) ctrs, err := state.PodContainers(testPod) @@ -2592,42 +2458,3 @@ func TestGetContainerConfigNonExistentIDFails(t *testing.T) { assert.Error(t, err) }) } - -// Test that the state will convert the ports to the new format -func TestConvertPortMapping(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testCtr, err := getTestCtr1(manager) - assert.NoError(t, err) - - ports := testCtr.config.PortMappings - - oldPorts := []types.OCICNIPortMapping{ - { - HostPort: 80, - ContainerPort: 90, - Protocol: "tcp", - HostIP: "192.168.3.3", - }, - { - HostPort: 100, - ContainerPort: 110, - Protocol: "udp", - HostIP: "192.168.4.4", - }, - } - - testCtr.config.OldPortMappings = oldPorts - testCtr.config.PortMappings = nil - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr.ID()) - assert.NoError(t, err) - - // set values to expected ones - testCtr.config.PortMappings = ports - - testContainersEqual(t, retrievedCtr, testCtr, true) - }) -} diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index 57a801aeab..8cfdce88a0 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -208,10 +208,6 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf)) } - if fs.Changed("db-backend") { - options = append(options, libpod.WithDatabaseBackend(cfg.ContainersConf.Engine.DBBackend)) - } - if cfg.CdiSpecDirs != nil { options = append(options, libpod.WithCDISpecDirs(cfg.CdiSpecDirs)) } diff --git a/pkg/specgenutil/util.go b/pkg/specgenutil/util.go index 638109a508..b1d5efddbd 100644 --- a/pkg/specgenutil/util.go +++ b/pkg/specgenutil/util.go @@ -282,7 +282,6 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf "--network-config-dir", config.Network.NetworkConfigDir, "--network-backend", config.Network.NetworkBackend, "--volumepath", config.Engine.VolumePath, - "--db-backend", config.Engine.DBBackend, fmt.Sprintf("--transient-store=%t", storageConfig.TransientStore), } for _, dir := range config.Engine.HooksDir.Get() { diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 7b665c7831..1b3eb9144d 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -332,9 +332,6 @@ func PodmanTestCreateUtil(tempDir string, target PodmanTestCreateUtilTarget) *Po os.Setenv("DISABLE_HC_SYSTEMD", "true") dbBackend := "sqlite" - if os.Getenv("PODMAN_DB") == "boltdb" { - dbBackend = "boltdb" - } networkBackend := Netavark networkConfigDir := "/etc/containers/networks" @@ -1377,8 +1374,8 @@ func (p *PodmanTestIntegration) makeOptions(args []string, options PodmanExecOpt eventsType = "none" } - podmanOptions := strings.Split(fmt.Sprintf("%s--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s --db-backend %s", - debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.NetworkConfigDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, eventsType, p.DatabaseBackend), " ") + podmanOptions := strings.Split(fmt.Sprintf("%s--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s", + debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.NetworkConfigDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, eventsType), " ") podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) if !options.NoCache { diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index 0fd0d27374..b4823a4c77 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -168,68 +168,6 @@ var _ = Describe("Podman Info", func() { Expect(session.OutputToString()).To(Equal("netavark")) }) - It("Podman info: check desired database backend", func() { - // defined in .cirrus.yml - want := os.Getenv("CI_DESIRED_DATABASE") - if want == "" { - if os.Getenv("CIRRUS_CI") == "" { - Skip("CI_DESIRED_DATABASE is not set--this is OK because we're not running under Cirrus") - } - Fail("CIRRUS_CI is set, but CI_DESIRED_DATABASE is not! See #16389") - } - session := podmanTest.Podman([]string{"info", "--format", "{{.Host.DatabaseBackend}}"}) - session.WaitWithDefaultTimeout() - Expect(session).To(ExitCleanly()) - Expect(session.OutputToString()).To(Equal(want)) - }) - - It("podman --db-backend info basic check", Serial, func() { - SkipIfRemote("--db-backend only supported on the local client") - - const desiredDB = "CI_DESIRED_DATABASE" - - type argWant struct { - arg string - want string - } - backends := []argWant{ - // default should be sqlite - {arg: "", want: "sqlite"}, - {arg: "boltdb", want: "boltdb"}, - // now because a boltdb exists it should use boltdb when default is requested - {arg: "", want: "boltdb"}, - {arg: "sqlite", want: "sqlite"}, - // just because we requested sqlite doesn't mean it stays that way. - // once a boltdb exists, podman will forevermore stick with it - {arg: "", want: "boltdb"}, - } - - for _, tt := range backends { - oldDesiredDB := os.Getenv(desiredDB) - if tt.arg == "boltdb" { - err := os.Setenv(desiredDB, "boltdb") - Expect(err).To(Not(HaveOccurred())) - defer os.Setenv(desiredDB, oldDesiredDB) - } - - session := podmanTest.Podman([]string{"--db-backend", tt.arg, "--log-level=info", "info", "--format", "{{.Host.DatabaseBackend}}"}) - session.WaitWithDefaultTimeout() - Expect(session).To(Exit(0)) - Expect(session.OutputToString()).To(Equal(tt.want)) - Expect(session.ErrorToString()).To(ContainSubstring("Using %s as database backend", tt.want)) - - if tt.arg == "boltdb" { - err := os.Setenv(desiredDB, oldDesiredDB) - Expect(err).To(Not(HaveOccurred())) - } - } - - // make sure we get an error for bogus values - session := podmanTest.Podman([]string{"--db-backend", "bogus", "info", "--format", "{{.Host.DatabaseBackend}}"}) - session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError(125, `Error: unsupported database backend: "bogus"`)) - }) - It("Podman info: check desired storage driver", func() { // defined in .cirrus.yml want := os.Getenv("CI_DESIRED_STORAGE") diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go index ec70c229a9..fad7f2c11c 100644 --- a/test/e2e/libpod_suite_remote_test.go +++ b/test/e2e/libpod_suite_remote_test.go @@ -122,8 +122,8 @@ func (p *PodmanTestIntegration) StopRemoteService() { // getRemoteOptions assembles all the podman main options func getRemoteOptions(p *PodmanTestIntegration, args []string) []string { networkDir := p.NetworkConfigDir - podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s --db-backend %s", - p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, networkDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, "file", p.DatabaseBackend), " ") + podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s", + p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, networkDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, "file"), " ") podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) podmanOptions = append(podmanOptions, args...) diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index 81ef930858..ed733f7f1b 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -167,11 +167,7 @@ var _ = Describe("Podman network connect and disconnect", func() { con2 := podmanTest.Podman([]string{"network", "connect", netName, "test"}) con2.WaitWithDefaultTimeout() - if podmanTest.DatabaseBackend == "boltdb" { - Expect(con2).Should(ExitWithError(125, fmt.Sprintf("container %s is already connected to network %q: network is already connected", cid, netName))) - } else { - Expect(con2).Should(ExitWithError(125, fmt.Sprintf("container %s is already connected to network %s: network is already connected", cid, netName))) - } + Expect(con2).Should(ExitWithError(125, fmt.Sprintf("container %s is already connected to network %s: network is already connected", cid, netName))) }) It("podman network connect", func() { diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index a835e139f5..1931c3c834 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -204,9 +204,6 @@ var _ = Describe("Podman pod rm", func() { session.WaitWithDefaultTimeout() // FIXME-someday: consolidate different error messages expect = "no pod with name or ID test1 found" - if podmanTest.DatabaseBackend == "boltdb" { - expect = "test1 is a container, not a pod" - } if IsRemote() { expect = `unable to find pod "test1"` } diff --git a/test/e2e/systemd_activate_test.go b/test/e2e/systemd_activate_test.go index 0d02cf12df..396d4c57c5 100644 --- a/test/e2e/systemd_activate_test.go +++ b/test/e2e/systemd_activate_test.go @@ -54,7 +54,7 @@ var _ = Describe("Systemd activate", func() { systemdArgs := []string{ "-E", "http_proxy", "-E", "https_proxy", "-E", "no_proxy", "-E", "HTTP_PROXY", "-E", "HTTPS_PROXY", "-E", "NO_PROXY", - "-E", "XDG_RUNTIME_DIR", "-E", "CI_DESIRED_DATABASE", + "-E", "XDG_RUNTIME_DIR", "--listen", addr, podmanTest.PodmanBinary} systemdArgs = append(systemdArgs, podmanOptions...) @@ -121,7 +121,7 @@ var _ = Describe("Systemd activate", func() { // start systemd activation with datagram socket activateSession := testUtils.StartSystemExec(activate, []string{ - "--datagram", "--listen", addr, "-E", "CI_DESIRED_DATABASE", + "--datagram", "--listen", addr, podmanTest.PodmanBinary, "--root=" + filepath.Join(tempdir, "server_root"), "system", "service", diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 3955a8059e..c2de153832 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -103,10 +103,6 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "rm", "myv"}) session.WaitWithDefaultTimeout() expect := "more than one result for volume name myv: volume already exists" - if podmanTest.DatabaseBackend == "boltdb" { - // boltdb issues volume name in quotes - expect = `more than one result for volume name "myv": volume already exists` - } Expect(session).To(ExitWithError(125, expect)) session = podmanTest.Podman([]string{"volume", "ls"}) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 1e807f3af6..10127dd3ed 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -103,28 +103,6 @@ host.slirp4netns.executable | $expr_path is "$output" "$CI_DESIRED_NETWORK" ".Host.NetworkBackend" } -@test "podman info - confirm desired database" { - # Always run this and preserve its value. We will check again in 999-*.bats - run_podman info --format '{{.Host.DatabaseBackend}}' - db_backend="$output" - echo "$db_backend" > $BATS_SUITE_TMPDIR/db-backend - - if [[ -z "$CI_DESIRED_DATABASE" ]]; then - # When running in Cirrus, CI_DESIRED_DATABASE *must* be defined - # in .cirrus.yml so we can double-check that all CI VMs are - # using netavark or cni as desired. - if [[ -n "$CIRRUS_CI" ]]; then - die "CIRRUS_CI is set, but CI_DESIRED_DATABASE is not! See #16389" - fi - - # Not running under Cirrus (e.g., gating tests, or dev laptop). - # Totally OK to skip this test. - skip "CI_DESIRED_DATABASE is unset--OK, because we're not in Cirrus" - fi - - is "$db_backend" "$CI_DESIRED_DATABASE" "CI_DESIRED_DATABASE (from .cirrus.yml)" -} - @test "podman info - confirm desired storage driver" { if [[ -z "$CI_DESIRED_STORAGE" ]]; then # When running in Cirrus, CI_DESIRED_STORAGE *must* be defined @@ -289,23 +267,6 @@ EOF } -# TODO 6.0: Remove this -@test "podman - BoltDB cannot create new databases" { - skip_if_remote "DB checks only work for local Podman" - - safe_opts=$(podman_isolation_opts ${PODMAN_TMPDIR}) - - CI_DESIRED_DATABASE= run_podman 125 $safe_opts --db-backend=boltdb info - assert "$output" =~ "deprecated, no new BoltDB databases can be created" \ - "without CI_DESIRED_DATABASE" - - CI_DESIRED_DATABASE=boltdb run_podman $safe_opts --log-level=debug --db-backend=boltdb info - assert "$output" =~ "Allowing deprecated database backend" \ - "with CI_DESIRED_DATABASE" - - SUPPRESS_BOLTDB_WARNING=true run_podman $safe_opts system reset --force -} - @test "podman - empty string defaults for certain values" { skip_if_remote "Test uses nonstandard paths for c/storage directories" diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 001404385a..843b2425c3 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -84,11 +84,6 @@ verify_iid_and_name() { @test "podman image scp transfer" { skip_if_remote "only applicable under local podman" - # See https://github.com/containers/podman/pull/21300 for details - if [[ "$CI_DESIRED_DATABASE" = "boltdb" ]]; then - skip "impossible due to pitfalls in our SSH implementation" - fi - # FIXME: Broken on debian SID; still broken 2024-09-11 # See https://github.com/containers/podman/pull/23020#issuecomment-2179284640 OS_RELEASE_ID="${OS_RELEASE_ID:-$(source /etc/os-release; echo $ID)}" diff --git a/test/system/999-final.bats b/test/system/999-final.bats deleted file mode 100644 index 4946a6d1eb..0000000000 --- a/test/system/999-final.bats +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bats -# -# Final set of tests to run. -# - -load helpers - -# Confirm that we're still using the same database we started with. -# -# This should never fail! If it does, it means that some test somewhere -# has run podman with --db-backend, which is known to wreak havoc. -# -# See https://github.com/containers/podman/issues/20563 -@test "podman database backend has not changed" { - # File is always written in 005-info.bats. It must always exist - # by the time we get here... - db_backend_file=$BATS_SUITE_TMPDIR/db-backend - - if [[ ! -e "$db_backend_file" ]]; then - # ...except in a manual run like "hack/bats 999" - if [[ $BATS_SUITE_TEST_NUMBER -le 5 ]]; then - skip "$db_backend_file missing, but this is a short manual bats run, so, ok" - fi - - die "Internal error: $db_backend_file does not exist! (check 005-*.bats)" - fi - - run_podman info --format '{{.Host.DatabaseBackend}}' - assert "$output" = "$(<$db_backend_file)" ".Host.DatabaseBackend has changed!" -} diff --git a/test/upgrade/test-upgrade.bats b/test/upgrade/test-upgrade.bats index 4760aac216..df065db14f 100644 --- a/test/upgrade/test-upgrade.bats +++ b/test/upgrade/test-upgrade.bats @@ -218,11 +218,7 @@ EOF # Whichever DB was picked by old_podman, make sure we honor it @test "info - database" { run_podman info --format '{{.Host.DatabaseBackend}}' - if version_is_older_than 4.8; then - assert "$output" = "boltdb" "DatabaseBackend for podman < 4.8" - else - assert "$output" = "sqlite" "DatabaseBackend for podman >= 4.8" - fi + assert "$output" = "sqlite" "DatabaseBackend for podman >= 4.8" } @test "images" {