mirror of
https://github.com/containers/podman.git
synced 2026-07-08 14:25:04 -04:00
249 lines
7.0 KiB
Go
249 lines
7.0 KiB
Go
//go:build !remote
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/containers/podman/v5/libpod/define"
|
|
"github.com/sirupsen/logrus"
|
|
"go.podman.io/common/pkg/config"
|
|
"go.podman.io/storage/pkg/fileutils"
|
|
)
|
|
|
|
// Migrate stops the rootless pause process and performs any necessary database
|
|
// migrations that are required. It can also migrate all containers to a new OCI
|
|
// runtime, if requested.
|
|
func (r *Runtime) Migrate(newRuntime string, migrateDB bool) error {
|
|
// Acquire the alive lock and hold it.
|
|
// Ensures that we don't let other Podman commands run while we are
|
|
// rewriting things in the DB.
|
|
aliveLock, err := r.getRuntimeAliveLock()
|
|
if err != nil {
|
|
return fmt.Errorf("retrieving alive lock: %w", err)
|
|
}
|
|
aliveLock.Lock()
|
|
defer aliveLock.Unlock()
|
|
|
|
if !r.valid {
|
|
return define.ErrRuntimeStopped
|
|
}
|
|
|
|
runningContainers, err := r.GetRunningContainers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allCtrs, err := r.state.AllContainers(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
logrus.Infof("Stopping all containers")
|
|
for _, ctr := range runningContainers {
|
|
fmt.Printf("stopped %s\n", ctr.ID())
|
|
if err := ctr.Stop(); err != nil {
|
|
return fmt.Errorf("cannot stop container %s: %w", ctr.ID(), err)
|
|
}
|
|
}
|
|
|
|
// Did the user request a new runtime?
|
|
runtimeChangeRequested := newRuntime != ""
|
|
var requestedRuntime OCIRuntime
|
|
if runtimeChangeRequested {
|
|
runtime, exists := r.ociRuntimes[newRuntime]
|
|
if !exists {
|
|
return fmt.Errorf("change to runtime %q requested but no such runtime is defined: %w", newRuntime, define.ErrInvalidArg)
|
|
}
|
|
requestedRuntime = runtime
|
|
}
|
|
|
|
for _, ctr := range allCtrs {
|
|
needsWrite := false
|
|
|
|
// Reset pause process location
|
|
oldLocation := filepath.Join(ctr.state.RunDir, "conmon.pid")
|
|
if ctr.config.ConmonPidFile == oldLocation {
|
|
logrus.Infof("Changing conmon PID file for %s", ctr.ID())
|
|
ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid")
|
|
needsWrite = true
|
|
}
|
|
|
|
// Reset runtime
|
|
if runtimeChangeRequested && ctr.config.OCIRuntime != newRuntime {
|
|
logrus.Infof("Resetting container %s runtime to runtime %s", ctr.ID(), newRuntime)
|
|
ctr.config.OCIRuntime = newRuntime
|
|
ctr.ociRuntime = requestedRuntime
|
|
|
|
needsWrite = true
|
|
}
|
|
|
|
if needsWrite {
|
|
if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil {
|
|
return fmt.Errorf("rewriting config for container %s: %w", ctr.ID(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if migrateDB {
|
|
if err := r.checkCanMigrate(); err != nil {
|
|
switch {
|
|
case errors.Is(err, errCannotMigrateSqlite):
|
|
fmt.Printf("No migration is necessary: %v", err)
|
|
return r.stopPauseProcess()
|
|
case errors.Is(err, errCannotMigrateHardcodedBolt):
|
|
logrus.Errorf("In containers.conf, database_backend is manually set to \"boltdb\" - comment this line out and run `podman system migrate --migrate-db` to complete migration to SQLite")
|
|
return fmt.Errorf("unable to migrate to SQLite database as database backend manually set")
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := r.migrateDB(); err != nil {
|
|
return fmt.Errorf("migrating database from BoltDB to SQLite: %w", err)
|
|
}
|
|
}
|
|
|
|
return r.stopPauseProcess()
|
|
}
|
|
|
|
var (
|
|
errCannotMigrateSqlite = errors.New("backing database is already SQLite")
|
|
errCannotMigrateHardcodedBolt = errors.New("database_backend in containers.conf is manually set to \"boltdb\"")
|
|
)
|
|
|
|
func (r *Runtime) checkCanMigrate() error {
|
|
if r.state.Type() == "sqlite" {
|
|
return errCannotMigrateSqlite
|
|
}
|
|
|
|
// Necessary as database configuration is overwritten when the state is set up.
|
|
// So we need a completely new state from disk to see what the user set.
|
|
newCfg, err := config.New(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("reloading configuration to check database backend in use")
|
|
}
|
|
backend, err := config.ParseDBBackend(newCfg.Engine.DBBackend)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid DB backend configured - please change containers.conf database_backend to \"sqlite\"")
|
|
}
|
|
if backend == config.DBBackendBoltDB {
|
|
return errCannotMigrateHardcodedBolt
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Runtime) migrateDB() error {
|
|
// Does the SQLite state already exist?
|
|
dbPath := sqliteStatePath(r)
|
|
if err := fileutils.Exists(dbPath); err == nil {
|
|
return fmt.Errorf("a SQLite database already exists at %s, refusing to overwrite", dbPath)
|
|
}
|
|
|
|
sqlState, err := NewSqliteState(r)
|
|
if err != nil {
|
|
return fmt.Errorf("creating SQLite database: %w", err)
|
|
}
|
|
|
|
// Recreate the cached paths in the new database
|
|
if err := sqlState.ValidateDBConfig(r); err != nil {
|
|
return fmt.Errorf("recreating config table: %w", err)
|
|
}
|
|
|
|
// Migrate volumes, then pods, then containers.
|
|
// Containers must be last as the pods they are part of and volumes they use must already exist.
|
|
allVolumes, err := r.state.AllVolumes()
|
|
if err != nil {
|
|
return fmt.Errorf("retrieving volumes from boltdb: %w", err)
|
|
}
|
|
for _, vol := range allVolumes {
|
|
if err := sqlState.AddVolume(vol); err != nil {
|
|
return err
|
|
}
|
|
if err := vol.update(); err != nil {
|
|
return err
|
|
}
|
|
if err := sqlState.SaveVolume(vol); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
allPods, err := r.state.AllPods()
|
|
if err != nil {
|
|
return fmt.Errorf("retrieving pods from boltdb: %w", err)
|
|
}
|
|
for _, pod := range allPods {
|
|
if err := sqlState.AddPod(pod); err != nil {
|
|
return err
|
|
}
|
|
if err := pod.updatePod(); err != nil {
|
|
return err
|
|
}
|
|
if err := sqlState.SavePod(pod); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Containers must be done as a graph due to dependencies.
|
|
// The state will error if we add a container before its dependencies.
|
|
allCtrs, err := r.state.AllContainers(true)
|
|
if err != nil {
|
|
return fmt.Errorf("retrieving containers from boltdb: %w", err)
|
|
}
|
|
|
|
// BoltDB doesn't actually populate container networks on initial pull
|
|
// from the database, that needs to be done separately.
|
|
for _, ctr := range allCtrs {
|
|
ctrNetworks, err := r.state.GetNetworks(ctr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctr.config.Networks = ctrNetworks
|
|
}
|
|
|
|
graph, err := BuildContainerGraph(allCtrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctrErrors := make(map[string]error)
|
|
ctrsVisited := make(map[string]bool)
|
|
|
|
for _, node := range graph.noDepNodes {
|
|
migrateNodeDatabase(node, false, ctrErrors, ctrsVisited, sqlState)
|
|
}
|
|
var ctrError error
|
|
for id, err := range ctrErrors {
|
|
if ctrError != nil {
|
|
logrus.Errorf("Migrating containers to SQLite: %v", ctrError)
|
|
}
|
|
ctrError = fmt.Errorf("migrating container %s: %w", id, err)
|
|
}
|
|
if ctrError != nil {
|
|
return ctrError
|
|
}
|
|
|
|
boltState := r.state
|
|
r.state = sqlState
|
|
|
|
// Move the Bolt database so it is not reused, but preserve so data is not lost.
|
|
boltDBPath := getBoltDBPath(r)
|
|
newBoltDBPath := fmt.Sprintf("%s-old", boltDBPath)
|
|
if err := os.Rename(boltDBPath, newBoltDBPath); err != nil {
|
|
// Restore the old state. Migration has failed.
|
|
r.state = boltState
|
|
|
|
return fmt.Errorf("renaming old database %s to %s: %w", boltDBPath, newBoltDBPath, err)
|
|
}
|
|
fmt.Printf("Old database has been renamed to %s and will no longer be used\n", newBoltDBPath)
|
|
|
|
// Only after we handle all failure cases that could require a revert.
|
|
boltState.Close()
|
|
|
|
return nil
|
|
}
|