mirror of
https://github.com/containers/podman.git
synced 2025-05-25 02:57:21 +08:00
435 lines
12 KiB
Go
435 lines
12 KiB
Go
package libpod
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/containers/storage"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
// Use SQLite backend for sql package
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
// SQLState is a state implementation backed by a persistent SQLite3 database
|
|
type SQLState struct {
|
|
db *sql.DB
|
|
specsDir string
|
|
runtime *Runtime
|
|
lock storage.Locker
|
|
valid bool
|
|
}
|
|
|
|
// NewSqlState initializes a SQL-backed state, created the database if necessary
|
|
func NewSQLState(dbPath, lockPath, specsDir string, runtime *Runtime) (State, error) {
|
|
state := new(SQLState)
|
|
|
|
state.runtime = runtime
|
|
|
|
// Make our lock file
|
|
lock, err := storage.GetLockfile(lockPath)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error creating lockfile for state")
|
|
}
|
|
state.lock = lock
|
|
|
|
// Make the directory that will hold JSON copies of container runtime specs
|
|
if err := os.MkdirAll(specsDir, 0750); err != nil {
|
|
// The directory is allowed to exist
|
|
if !os.IsExist(err) {
|
|
return nil, errors.Wrapf(err, "error creating OCI specs dir %s", specsDir)
|
|
}
|
|
}
|
|
state.specsDir = specsDir
|
|
|
|
// Acquire the lock while we open the database and perform initial setup
|
|
state.lock.Lock()
|
|
defer state.lock.Unlock()
|
|
|
|
// TODO add a separate temporary database for per-boot container
|
|
// state
|
|
|
|
// Open the database
|
|
// Use loc=auto to get accurate locales for timestamps
|
|
db, err := sql.Open("sqlite3", dbPath+"?_loc=auto")
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error opening database")
|
|
}
|
|
|
|
// Ensure connectivity
|
|
if err := db.Ping(); err != nil {
|
|
return nil, errors.Wrapf(err, "cannot establish connection to database")
|
|
}
|
|
|
|
// Prepare database
|
|
if err := prepareDB(db); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.db = db
|
|
|
|
state.valid = true
|
|
|
|
return state, nil
|
|
}
|
|
|
|
// Close the state's database connection
|
|
func (s *SQLState) Close() error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
if !s.valid {
|
|
return ErrDBClosed
|
|
}
|
|
|
|
s.valid = false
|
|
|
|
if err := s.db.Close(); err != nil {
|
|
return errors.Wrapf(err, "error closing database")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Container retrieves a container from its full ID
|
|
func (s *SQLState) Container(id string) (*Container, error) {
|
|
const query = `SELECT containers.*,
|
|
containerState.State,
|
|
containerState.ConfigPath,
|
|
containerState.RunDir,
|
|
containerState.MountPoint,
|
|
containerState.StartedTime,
|
|
containerState.FinishedTime,
|
|
containerState.ExitCode
|
|
FROM containers
|
|
INNER JOIN
|
|
containerState ON containers.Id = containerState.Id
|
|
WHERE containers.Id=?;`
|
|
|
|
if !s.valid {
|
|
return nil, ErrDBClosed
|
|
}
|
|
|
|
row := s.db.QueryRow(query, id)
|
|
|
|
ctr, err := ctrFromScannable(row, s.runtime, s.specsDir)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving container %s from database", id)
|
|
}
|
|
|
|
return ctr, nil
|
|
}
|
|
|
|
// LookupContainer retrieves a container by full or unique partial ID or name
|
|
func (s *SQLState) LookupContainer(idOrName string) (*Container, error) {
|
|
const query = `SELECT containers.*,
|
|
containerState.State,
|
|
containerState.ConfigPath,
|
|
containerState.RunDir,
|
|
containerState.MountPoint,
|
|
containerState.StartedTime,
|
|
containerState.FinishedTime,
|
|
containerState.ExitCode
|
|
FROM containers
|
|
INNER JOIN
|
|
containerState ON containers.Id = containerState.Id
|
|
WHERE (containers.Id LIKE ?) OR containers.Name=?;`
|
|
|
|
if !s.valid {
|
|
return nil, ErrDBClosed
|
|
}
|
|
|
|
rows, err := s.db.Query(query, idOrName+"%", idOrName)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving container %s row from database", idOrName)
|
|
}
|
|
defer rows.Close()
|
|
|
|
foundResult := false
|
|
var ctr *Container
|
|
for rows.Next() {
|
|
if foundResult {
|
|
return nil, errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
|
|
}
|
|
|
|
var err error
|
|
ctr, err = ctrFromScannable(rows, s.runtime, s.specsDir)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving container %s from database", idOrName)
|
|
}
|
|
foundResult = true
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving rows for container ID or name %s", idOrName)
|
|
}
|
|
|
|
if !foundResult {
|
|
return nil, errors.Wrapf(ErrNoSuchCtr, "no container with ID or name %s found", idOrName)
|
|
}
|
|
|
|
return ctr, nil
|
|
}
|
|
|
|
// HasContainer checks if the given container is present in the state
|
|
// It accepts a full ID
|
|
func (s *SQLState) HasContainer(id string) (bool, error) {
|
|
const query = "SELECT 1 FROM containers WHERE Id=?;"
|
|
|
|
if !s.valid {
|
|
return false, ErrDBClosed
|
|
}
|
|
|
|
row := s.db.QueryRow(query, id)
|
|
|
|
var check int
|
|
err := row.Scan(&check)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return false, nil
|
|
}
|
|
|
|
return false, errors.Wrapf(err, "error questing database for existence of container %s", id)
|
|
} else if check != 1 {
|
|
return false, errors.Wrapf(ErrInternal, "check digit for HasContainer query incorrect")
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// AddContainer adds the given container to the state
|
|
// If the container belongs to a pod, that pod must already be present in the
|
|
// state, and the container will be added to the pod
|
|
func (s *SQLState) AddContainer(ctr *Container) (err error) {
|
|
const (
|
|
addCtr = `INSERT INTO containers VALUES (
|
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
);`
|
|
addCtrState = `INSERT INTO containerState VALUES (
|
|
?, ?, ?, ?, ?, ?, ?, ?
|
|
);`
|
|
)
|
|
|
|
if !s.valid {
|
|
return ErrDBClosed
|
|
}
|
|
|
|
if !ctr.valid {
|
|
return ErrCtrRemoved
|
|
}
|
|
|
|
labelsJSON, err := json.Marshal(ctr.config.Labels)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error marshaling container %s labels to JSON", ctr.ID())
|
|
}
|
|
|
|
// Save the container's runtime spec to disk
|
|
specJSON, err := json.Marshal(ctr.config.Spec)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID())
|
|
}
|
|
specPath := getSpecPath(s.specsDir, ctr.ID())
|
|
if err := ioutil.WriteFile(specPath, specJSON, 0750); err != nil {
|
|
return errors.Wrapf(err, "error saving container %s spec JSON to disk", ctr.ID())
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
if err2 := os.Remove(specPath); err2 != nil {
|
|
logrus.Errorf("Error removing container %s JSON spec from state: %v", ctr.ID(), err2)
|
|
}
|
|
}
|
|
}()
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error beginning database transaction")
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
if err2 := tx.Rollback(); err2 != nil {
|
|
logrus.Errorf("Error rolling back transaction to add container %s: %v", ctr.ID(), err2)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Add static container information
|
|
_, err = tx.Exec(addCtr,
|
|
ctr.ID(),
|
|
ctr.Name(),
|
|
ctr.config.MountLabel,
|
|
ctr.config.StaticDir,
|
|
boolToSQL(ctr.config.Stdin),
|
|
string(labelsJSON),
|
|
ctr.config.StopSignal,
|
|
timeToSQL(ctr.config.CreatedTime),
|
|
ctr.config.RootfsImageID,
|
|
ctr.config.RootfsImageName,
|
|
boolToSQL(ctr.config.UseImageConfig))
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error adding static information for container %s to database", ctr.ID())
|
|
}
|
|
|
|
// Add container state to the database
|
|
_, err = tx.Exec(addCtrState,
|
|
ctr.ID(),
|
|
ctr.state.State,
|
|
ctr.state.ConfigPath,
|
|
ctr.state.RunDir,
|
|
ctr.state.Mountpoint,
|
|
timeToSQL(ctr.state.StartedTime),
|
|
timeToSQL(ctr.state.FinishedTime),
|
|
ctr.state.ExitCode)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error adding container %s state to database", ctr.ID())
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveContainer removes the container from the state
|
|
func (s *SQLState) RemoveContainer(ctr *Container) error {
|
|
const (
|
|
removeCtr = "DELETE FROM containers WHERE Id=?;"
|
|
removeState = "DELETE FROM containerState WHERE ID=?;"
|
|
)
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
ctr.lock.Lock()
|
|
defer ctr.lock.Unlock()
|
|
|
|
if !s.valid {
|
|
return ErrDBClosed
|
|
}
|
|
|
|
committed := false
|
|
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error beginning database transaction")
|
|
}
|
|
defer func() {
|
|
if err != nil && !committed {
|
|
if err2 := tx.Rollback(); err2 != nil {
|
|
logrus.Errorf("Error rolling back transaction to add container %s: %v", ctr.ID(), err2)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Check rows acted on for the first transaction, verify we actually removed something
|
|
result, err := tx.Exec(removeCtr, ctr.ID())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error removing container %s from containers table", ctr.ID())
|
|
}
|
|
rows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error retrieving number of rows in transaction removing container %s", ctr.ID())
|
|
} else if rows == 0 {
|
|
return ErrNoSuchCtr
|
|
}
|
|
|
|
if _, err := tx.Exec(removeState, ctr.ID()); err != nil {
|
|
return errors.Wrapf(err, "error removing container %s from state table", ctr.ID())
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return errors.Wrapf(err, "error committing transaction to remove container %s", ctr.ID())
|
|
}
|
|
|
|
committed = true
|
|
|
|
// Remove the container's JSON from disk
|
|
jsonPath := getSpecPath(s.specsDir, ctr.ID())
|
|
if err := os.Remove(jsonPath); err != nil {
|
|
return errors.Wrapf(err, "error removing JSON spec from state for container %s", ctr.ID())
|
|
}
|
|
|
|
ctr.valid = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// AllContainers retrieves all the containers presently in the state
|
|
func (s *SQLState) AllContainers() ([]*Container, error) {
|
|
// TODO maybe do an ORDER BY here?
|
|
const query = `SELECT containers.*,
|
|
containerState.State,
|
|
containerState.ConfigPath,
|
|
containerState.RunDir,
|
|
containerState.MountPoint,
|
|
containerState.StartedTime,
|
|
containerState.FinishedTime,
|
|
containerState.ExitCode
|
|
FROM containers
|
|
INNER JOIN
|
|
containerState ON containers.Id = containerState.Id;`
|
|
|
|
if !s.valid {
|
|
return nil, ErrDBClosed
|
|
}
|
|
|
|
rows, err := s.db.Query(query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving containers from database")
|
|
}
|
|
defer rows.Close()
|
|
|
|
containers := []*Container{}
|
|
|
|
for rows.Next() {
|
|
ctr, err := ctrFromScannable(rows, s.runtime, s.specsDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
containers = append(containers, ctr)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, errors.Wrapf(err, "error retrieving container rows")
|
|
}
|
|
|
|
return containers, nil
|
|
}
|
|
|
|
// Pod retrieves a pod by its full ID
|
|
func (s *SQLState) Pod(id string) (*Pod, error) {
|
|
return nil, ErrNotImplemented
|
|
}
|
|
|
|
// LookupPod retrieves a pot by full or unique partial ID or name
|
|
func (s *SQLState) LookupPod(idOrName string) (*Pod, error) {
|
|
return nil, ErrNotImplemented
|
|
}
|
|
|
|
// HasPod checks if a pod exists given its full ID
|
|
func (s *SQLState) HasPod(id string) (bool, error) {
|
|
return false, ErrNotImplemented
|
|
}
|
|
|
|
// AddPod adds a pod to the state
|
|
// Only empty pods can be added to the state
|
|
func (s *SQLState) AddPod(pod *Pod) error {
|
|
return ErrNotImplemented
|
|
}
|
|
|
|
// RemovePod removes a pod from the state
|
|
// Only empty pods can be removed
|
|
func (s *SQLState) RemovePod(pod *Pod) error {
|
|
return ErrNotImplemented
|
|
}
|
|
|
|
// AllPods retrieves all pods presently in the state
|
|
func (s *SQLState) AllPods() ([]*Pod, error) {
|
|
return nil, ErrNotImplemented
|
|
}
|