diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/runtime.go | 19 | ||||
-rw-r--r-- | libpod/sql_state.go | 1091 | ||||
-rw-r--r-- | libpod/sql_state_internal.go | 1137 | ||||
-rw-r--r-- | libpod/state_test.go | 30 |
4 files changed, 2 insertions, 2275 deletions
diff --git a/libpod/runtime.go b/libpod/runtime.go index 5c3705ee9..39c65548b 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -30,6 +30,7 @@ const ( // reboot InMemoryStateStore RuntimeStateStore = iota // SQLiteStateStore is a state backed by a SQLite database + // It is presently disabled SQLiteStateStore RuntimeStateStore = iota // BoltDBStateStore is a state backed by a BoltDB database BoltDBStateStore RuntimeStateStore = iota @@ -386,23 +387,7 @@ func makeRuntime(runtime *Runtime) error { } runtime.state = state case SQLiteStateStore: - dbPath := filepath.Join(runtime.config.StaticDir, "sql_state.db") - specsDir := filepath.Join(runtime.config.StaticDir, "ocispec") - - // Make a directory to hold JSON versions of container OCI specs - if err := os.MkdirAll(specsDir, 0755); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return errors.Wrapf(err, "error creating runtime OCI specs directory %s", - specsDir) - } - } - - state, err := NewSQLState(dbPath, specsDir, runtime.lockDir, runtime) - if err != nil { - return err - } - runtime.state = state + return errors.Wrapf(ErrInvalidArg, "SQLite state is currently disabled") case BoltDBStateStore: dbPath := filepath.Join(runtime.config.StaticDir, "bolt_state.db") diff --git a/libpod/sql_state.go b/libpod/sql_state.go deleted file mode 100644 index f583febda..000000000 --- a/libpod/sql_state.go +++ /dev/null @@ -1,1091 +0,0 @@ -package libpod - -import ( - "database/sql" - "encoding/json" - "os" - - "github.com/containernetworking/cni/pkg/types" - cnitypes "github.com/containernetworking/cni/pkg/types/current" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - // Use SQLite backend for sql package - _ "github.com/mattn/go-sqlite3" -) - -// DBSchema is the current DB schema version -// Increments every time a change is made to the database's tables -const DBSchema = 14 - -// SQLState is a state implementation backed by a persistent SQLite3 database -type SQLState struct { - db *sql.DB - specsDir string - lockDir string - runtime *Runtime - valid bool -} - -// NewSQLState initializes a SQL-backed state, created the database if necessary -func NewSQLState(dbPath, specsDir, lockDir string, runtime *Runtime) (State, error) { - state := new(SQLState) - - state.runtime = runtime - - // 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 - - // Make the directory that will hold container lockfiles - if err := os.MkdirAll(lockDir, 0750); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return nil, errors.Wrapf(err, "error creating lockfiles dir %s", lockDir) - } - } - state.lockDir = lockDir - - // 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 - } - - // Ensure that the database matches our config - if err := checkDB(db, runtime); 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 { - if !s.valid { - return ErrDBClosed - } - - s.valid = false - - if err := s.db.Close(); err != nil { - return errors.Wrapf(err, "error closing database") - } - - return nil -} - -// Refresh clears the state after a reboot -// Resets mountpoint, PID, state, netns path for all containers -func (s *SQLState) Refresh() (err error) { - const refresh = `UPDATE containerState SET - State=?, - Mountpoint=?, - Pid=?, - NetNSPath=?, - ExecSessions=?, - IPs=?, - Routes=?, - BindMounts=?;` - - 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 refresh state: %v", err2) - } - } - }() - - // Refresh container state - // The constants could be moved into the SQL, but keeping them here - // will keep us in sync in case ContainerStateConfigured ever changes in - // the container state - _, err = tx.Exec(refresh, - ContainerStateConfigured, - "", - 0, - "", - "{}", - "[]", - "[]", - "{}") - if err != nil { - return errors.Wrapf(err, "error refreshing database state") - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to refresh database") - } - - return nil -} - -// Container retrieves a container from its full ID -func (s *SQLState) Container(id string) (*Container, error) { - const query = containerQuery + "WHERE containers.Id=?;" - - if id == "" { - return nil, ErrEmptyID - } - - if !s.valid { - return nil, ErrDBClosed - } - - row := s.db.QueryRow(query, id) - - ctr, err := s.ctrFromScannable(row) - 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 = containerQuery + "WHERE (containers.Id LIKE ?) OR containers.Name=?;" - - if idOrName == "" { - return nil, ErrEmptyID - } - - 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 = s.ctrFromScannable(rows) - 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 id == "" { - return false, ErrEmptyID - } - - 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) { - if !ctr.valid { - return ErrCtrRemoved - } - - if ctr.config.Pod != "" { - return errors.Wrapf(ErrPodExists, "cannot add container that belongs to a pod, use AddContainerToPod instead") - } - - return s.addContainer(ctr, nil) -} - -// UpdateContainer updates a container's state from the database -func (s *SQLState) UpdateContainer(ctr *Container) error { - const query = `SELECT State, - ConfigPath, - RunDir, - Mountpoint, - StartedTime, - FinishedTime, - ExitCode, - OomKilled, - Pid, - NetNSPath, - ExecSessions, - IPs, - Routes, - BindMounts - FROM containerState WHERE ID=?;` - - var ( - state int - configPath string - runDir string - mountpoint string - startedTimeString string - finishedTimeString string - exitCode int32 - oomKilled int - pid int - netNSPath string - execSessions string - ipsJSON string - routesJSON string - bindMountsJSON string - ) - - if !s.valid { - return ErrDBClosed - } - - if !ctr.valid { - return ErrCtrRemoved - } - - row := s.db.QueryRow(query, ctr.ID()) - err := row.Scan( - &state, - &configPath, - &runDir, - &mountpoint, - &startedTimeString, - &finishedTimeString, - &exitCode, - &oomKilled, - &pid, - &netNSPath, - &execSessions, - &ipsJSON, - &routesJSON, - &bindMountsJSON) - if err != nil { - // The container may not exist in the database - if err == sql.ErrNoRows { - // Assume that the container was removed by another process - // As such make it invalid - ctr.valid = false - - return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in database", ctr.ID()) - } - - return errors.Wrapf(err, "error parsing database state for container %s", ctr.ID()) - } - - newState := new(containerState) - newState.State = ContainerStatus(state) - newState.ConfigPath = configPath - newState.RunDir = runDir - newState.Mountpoint = mountpoint - newState.ExitCode = exitCode - newState.OOMKilled = boolFromSQL(oomKilled) - newState.PID = pid - - newState.ExecSessions = make(map[string]*ExecSession) - if err := json.Unmarshal([]byte(execSessions), &newState.ExecSessions); err != nil { - return errors.Wrapf(err, "error parsing container %s exec sessions", ctr.ID()) - } - - ips := []*cnitypes.IPConfig{} - if err := json.Unmarshal([]byte(ipsJSON), &ips); err != nil { - return errors.Wrapf(err, "error parsing container %s IPs JSON", ctr.ID()) - } - if len(ips) > 0 { - newState.IPs = ips - } - - routes := []*types.Route{} - if err := json.Unmarshal([]byte(routesJSON), &routes); err != nil { - return errors.Wrapf(err, "error parsing container %s routes JSON", ctr.ID()) - } - if len(routes) > 0 { - newState.Routes = routes - } - - bindMounts := make(map[string]string) - if err := json.Unmarshal([]byte(bindMountsJSON), &bindMounts); err != nil { - return errors.Wrapf(err, "error parsing container %s bind mounts JSON", ctr.ID()) - } - newState.BindMounts = bindMounts - - if newState.Mountpoint != "" { - newState.Mounted = true - } - - startedTime, err := timeFromSQL(startedTimeString) - if err != nil { - return errors.Wrapf(err, "error parsing container %s started time", ctr.ID()) - } - newState.StartedTime = startedTime - - finishedTime, err := timeFromSQL(finishedTimeString) - if err != nil { - return errors.Wrapf(err, "error parsing container %s finished time", ctr.ID()) - } - newState.FinishedTime = finishedTime - - // Do we need to replace the container's netns? - if netNSPath != "" { - // Check if the container's old state has a good netns - if ctr.state.NetNS != nil && netNSPath == ctr.state.NetNS.Path() { - newState.NetNS = ctr.state.NetNS - } else { - // Tear down the existing namespace - if err := s.runtime.teardownNetNS(ctr); err != nil { - return err - } - - // Open the new network namespace - ns, err := joinNetNS(netNSPath) - if err != nil { - return errors.Wrapf(err, "error joining network namespace for container %s", ctr.ID()) - } - newState.NetNS = ns - } - } else { - // The container no longer has a network namespace - // Tear down the old one - if err := s.runtime.teardownNetNS(ctr); err != nil { - return err - } - } - - // New state compiled successfully, swap it into the current state - ctr.state = newState - - return nil -} - -// SaveContainer updates a container's state in the database -func (s *SQLState) SaveContainer(ctr *Container) (err error) { - const update = `UPDATE containerState SET - State=?, - ConfigPath=?, - RunDir=?, - Mountpoint=?, - StartedTime=?, - FinishedTime=?, - ExitCode=?, - OomKilled=?, - Pid=?, - NetNSPath=?, - ExecSessions=?, - IPs=?, - Routes=?, - BindMounts=? - WHERE Id=?;` - - if !ctr.valid { - return ErrCtrRemoved - } - - execSessionsJSON, err := json.Marshal(ctr.state.ExecSessions) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s exec sessions", ctr.ID()) - } - - netNSPath := "" - if ctr.state.NetNS != nil { - netNSPath = ctr.state.NetNS.Path() - } - - ipsJSON, err := json.Marshal(ctr.state.IPs) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s IPs to JSON", ctr.ID()) - } - - routesJSON, err := json.Marshal(ctr.state.Routes) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s routes to JSON", ctr.ID()) - } - - bindMountsJSON, err := json.Marshal(ctr.state.BindMounts) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s bind mounts to JSON", ctr.ID()) - } - - 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) - } - } - }() - - // Add container state to the database - result, err := tx.Exec(update, - ctr.state.State, - ctr.state.ConfigPath, - ctr.state.RunDir, - ctr.state.Mountpoint, - timeToSQL(ctr.state.StartedTime), - timeToSQL(ctr.state.FinishedTime), - ctr.state.ExitCode, - boolToSQL(ctr.state.OOMKilled), - ctr.state.PID, - netNSPath, - execSessionsJSON, - ipsJSON, - routesJSON, - bindMountsJSON, - ctr.ID()) - if err != nil { - return errors.Wrapf(err, "error updating container %s state in database", ctr.ID()) - } - rows, err := result.RowsAffected() - if err != nil { - return errors.Wrapf(err, "error retrieving number of rows modified by update of container %s", ctr.ID()) - } - if rows == 0 { - // Container was probably removed elsewhere - ctr.valid = false - return ErrNoSuchCtr - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to update container %s", ctr.ID()) - } - - return nil -} - -// ContainerInUse checks if other containers depend on the given container -// It returns the IDs of containers which depend on the given container -func (s *SQLState) ContainerInUse(ctr *Container) ([]string, error) { - const inUseQuery = `SELECT Id FROM containers WHERE - IPCNsCtr=? OR - MountNsCtr=? OR - NetNsCtr=? OR - PIDNsCtr=? OR - UserNsCtr=? OR - UTSNsCtr=? OR - CgroupNsCtr=?;` - - if !s.valid { - return nil, ErrDBClosed - } - - if !ctr.valid { - return nil, ErrCtrRemoved - } - - id := ctr.ID() - - rows, err := s.db.Query(inUseQuery, id, id, id, id, id, id, id) - if err != nil { - return nil, errors.Wrapf(err, "error querying database for containers that depend on container %s", id) - } - defer rows.Close() - - ids := []string{} - - for rows.Next() { - var ctrID string - if err := rows.Scan(&ctrID); err != nil { - return nil, errors.Wrapf(err, "error scanning container IDs from db rows for container %s", id) - } - - ids = append(ids, ctrID) - } - if err := rows.Err(); err != nil { - return nil, errors.Wrapf(err, "error retrieving rows for container %s", id) - } - - return ids, nil -} - -// RemoveContainer removes the given container from the state -func (s *SQLState) RemoveContainer(ctr *Container) error { - if ctr.config.Pod != "" { - return errors.Wrapf(ErrPodExists, "container %s belongs to a pod, use RemoveContainerFromPod", ctr.ID()) - } - - return s.removeContainer(ctr, nil) -} - -// AllContainers retrieves all the containers presently in the state -func (s *SQLState) AllContainers() ([]*Container, error) { - const query = containerQuery + ";" - - 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 := s.ctrFromScannable(rows) - 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) { - const query = "SELECT * FROM pods WHERE Id=?;" - - if !s.valid { - return nil, ErrDBClosed - } - - if id == "" { - return nil, ErrEmptyID - } - - row := s.db.QueryRow(query, id) - - pod, err := s.podFromScannable(row) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving pod %s from database", id) - } - - return pod, nil -} - -// LookupPod retrieves a pot by full or unique partial ID or name -func (s *SQLState) LookupPod(idOrName string) (*Pod, error) { - const query = "SELECT * FROM pods WHERE (Id LIKE ?) OR Name=?;" - - if idOrName == "" { - return nil, ErrEmptyID - } - - if !s.valid { - return nil, ErrDBClosed - } - - rows, err := s.db.Query(query, idOrName+"%", idOrName) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving pod %s row from database", idOrName) - } - defer rows.Close() - - foundResult := false - var pod *Pod - for rows.Next() { - if foundResult { - return nil, errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) - } - - var err error - pod, err = s.podFromScannable(rows) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving pod %s from database", idOrName) - } - foundResult = true - } - if err := rows.Err(); err != nil { - return nil, errors.Wrapf(err, "error retrieving rows for pod ID or name %s", idOrName) - } - - if !foundResult { - return nil, errors.Wrapf(ErrNoSuchCtr, "no pod with ID or name %s found", idOrName) - } - - return pod, nil -} - -// HasPod checks if a pod exists given its full ID -func (s *SQLState) HasPod(id string) (bool, error) { - if id == "" { - return false, ErrEmptyID - } - - if !s.valid { - return false, ErrDBClosed - } - - return s.podExists(id) -} - -// PodHasContainer checks if the given pod containers a container with the given -// ID -func (s *SQLState) PodHasContainer(pod *Pod, ctrID string) (bool, error) { - const query = "SELECT 1 FROM containers WHERE Id=? AND Pod=?;" - - if ctrID == "" { - return false, ErrEmptyID - } - - if !s.valid { - return false, ErrDBClosed - } - - if !pod.valid { - return false, ErrPodRemoved - } - - row := s.db.QueryRow(query, ctrID, pod.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", ctrID) - } else if check != 1 { - return false, errors.Wrapf(ErrInternal, "check digit for PodHasContainer query incorrect") - } - - return true, nil -} - -// PodContainersByID returns the container IDs of all containers in the given -// pod -func (s *SQLState) PodContainersByID(pod *Pod) ([]string, error) { - const query = "SELECT Id FROM containers WHERE Pod=?;" - - if !s.valid { - return nil, ErrDBClosed - } - - if !pod.valid { - return nil, ErrPodRemoved - } - - // Check to make sure pod still exists in DB - exists, err := s.podExists(pod.ID()) - if err != nil { - return nil, err - } - if !exists { - pod.valid = false - return nil, ErrPodRemoved - } - - // Get actual containers - rows, err := s.db.Query(query, pod.ID()) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving containers from database") - } - defer rows.Close() - - containers := []string{} - - for rows.Next() { - var id string - if err := rows.Scan(&id); err != nil { - if err == sql.ErrNoRows { - return nil, ErrNoSuchCtr - } - - return nil, errors.Wrapf(err, "error parsing database row into container ID") - } - - containers = append(containers, id) - } - if err := rows.Err(); err != nil { - return nil, errors.Wrapf(err, "error retrieving container rows") - } - - return containers, nil -} - -// PodContainers returns all the containers in a pod given the pod's full ID -func (s *SQLState) PodContainers(pod *Pod) ([]*Container, error) { - const query = containerQuery + "WHERE containers.Pod=?;" - - if !s.valid { - return nil, ErrDBClosed - } - - if !pod.valid { - return nil, ErrPodRemoved - } - - // Check to make sure pod still exists in DB - exists, err := s.podExists(pod.ID()) - if err != nil { - return nil, err - } - if !exists { - pod.valid = false - return nil, ErrPodRemoved - } - - // Get actual containers - rows, err := s.db.Query(query, pod.ID()) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving containers from database") - } - defer rows.Close() - - containers := []*Container{} - - for rows.Next() { - ctr, err := s.ctrFromScannable(rows) - 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 -} - -// AddPod adds a pod to the state -// Only empty pods can be added to the state -func (s *SQLState) AddPod(pod *Pod) (err error) { - const ( - podQuery = "INSERT INTO pods VALUES (?, ?, ?);" - registryQuery = "INSERT INTO registry VALUES (?, ?);" - ) - - if !s.valid { - return ErrDBClosed - } - - if !pod.valid { - return ErrPodRemoved - } - - labelsJSON, err := json.Marshal(pod.config.Labels) - if err != nil { - return errors.Wrapf(err, "error marshaling pod %s labels to JSON", pod.ID()) - } - - 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 pod %s: %v", pod.ID(), err2) - } - } - }() - - if _, err := tx.Exec(registryQuery, pod.ID(), pod.Name()); err != nil { - return errors.Wrapf(err, "error adding pod %s to name/ID registry", pod.ID()) - } - - if _, err = tx.Exec(podQuery, pod.ID(), pod.Name(), string(labelsJSON)); err != nil { - return errors.Wrapf(err, "error adding pod %s to database", pod.ID()) - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to add pod %s", pod.ID()) - } - - return nil -} - -// RemovePod removes a pod from the state -// Only empty pods can be removed -func (s *SQLState) RemovePod(pod *Pod) (err error) { - const ( - removePod = "DELETE FROM pods WHERE ID=?;" - removeRegistry = "DELETE FROM registry WHERE Id=?;" - ) - - 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 remove pod %s: %v", pod.ID(), err2) - } - } - }() - - // Check rows acted on for the first statement, verify we actually removed something - result, err := tx.Exec(removePod, pod.ID()) - if err != nil { - return errors.Wrapf(err, "error removing pod %s from pods table", pod.ID()) - } - rows, err := result.RowsAffected() - if err != nil { - return errors.Wrapf(err, "error retrieving number of rows in transaction removing pod %s", pod.ID()) - } else if rows == 0 { - pod.valid = false - return ErrNoSuchPod - } - - // We know it exists, remove it from registry - if _, err := tx.Exec(removeRegistry, pod.ID()); err != nil { - return errors.Wrapf(err, "error removing pod %s from name/ID registry", pod.ID()) - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to remove pod %s", pod.ID()) - } - - return nil -} - -// RemovePodContainers removes all containers in a pod simultaneously -// This can avoid issues with dependencies within the pod -// The operation will fail if any container in the pod has a dependency from -// outside the pod -func (s *SQLState) RemovePodContainers(pod *Pod) (err error) { - const ( - getPodCtrs = "SELECT Id FROM containers WHERE pod=?;" - removeCtr = "DELETE FROM containers WHERE pod=?;" - removeCtrState = "DELETE FROM containerState WHERE ID IN (SELECT Id FROM containers WHERE pod=?);" - ) - - if !s.valid { - return ErrDBClosed - } - - if !pod.valid { - return ErrPodRemoved - } - - 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 remove pod %s containers: %v", pod.ID(), err2) - } - } - }() - - // Check if the pod exists - exists, err := podExistsTx(pod.ID(), tx) - if err != nil { - return err - } - if !exists { - pod.valid = false - return ErrNoSuchPod - } - - // First get all containers in the pod - rows, err := tx.Query(getPodCtrs, pod.ID()) - if err != nil { - return errors.Wrapf(err, "error retrieving containers from database") - } - defer rows.Close() - - containers := []string{} - - for rows.Next() { - var id string - if err := rows.Scan(&id); err != nil { - if err == sql.ErrNoRows { - return ErrNoSuchCtr - } - - return errors.Wrapf(err, "error parsing database row into container ID") - } - - containers = append(containers, id) - } - if err := rows.Err(); err != nil { - return errors.Wrapf(err, "error retrieving container rows") - } - - // Have container IDs, now exec SQL to remove containers in the pod - // Remove state first, as it needs the subquery on containers - // Don't bother checking if we actually removed anything, we just want to - // empty the pod - if _, err := tx.Exec(removeCtrState, pod.ID()); err != nil { - return errors.Wrapf(err, "error removing pod %s containers from state table", pod.ID()) - } - - if _, err := tx.Exec(removeCtr, pod.ID()); err != nil { - return errors.Wrapf(err, "error removing pod %s containers from containers table", pod.ID()) - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction remove pod %s containers", pod.ID()) - } - - // Remove JSON files from the containers in question - hasError := false - for _, ctr := range containers { - jsonPath := getSpecPath(s.specsDir, ctr) - if err := os.Remove(jsonPath); err != nil { - logrus.Errorf("Error removing spec JSON for container %s: %v", ctr, err) - hasError = true - } - - portsPath := getPortsPath(s.specsDir, ctr) - if err := os.Remove(portsPath); err != nil { - if !os.IsNotExist(err) { - logrus.Errorf("Error removing ports JSON for container %s: %v", ctr, err) - hasError = true - } - } - } - if hasError { - return errors.Wrapf(ErrInternal, "error removing JSON state for some containers") - } - - return nil -} - -// AddContainerToPod adds a container to the given pod -func (s *SQLState) AddContainerToPod(pod *Pod, ctr *Container) error { - if !pod.valid { - return ErrPodRemoved - } - - if !ctr.valid { - return ErrCtrRemoved - } - - if ctr.config.Pod != pod.ID() { - return errors.Wrapf(ErrInvalidArg, "container's pod ID does not match given pod's ID") - } - - return s.addContainer(ctr, pod) -} - -// RemoveContainerFromPod removes a container from the given pod -func (s *SQLState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { - if ctr.config.Pod != pod.ID() { - return errors.Wrapf(ErrInvalidArg, "container %s is not in pod %s", ctr.ID(), pod.ID()) - } - - return s.removeContainer(ctr, pod) -} - -// AllPods retrieves all pods presently in the state -func (s *SQLState) AllPods() ([]*Pod, error) { - const query = "SELECT * FROM pods;" - - if !s.valid { - return nil, ErrDBClosed - } - - rows, err := s.db.Query(query) - if err != nil { - return nil, errors.Wrapf(err, "error querying database for all pods") - } - defer rows.Close() - - pods := []*Pod{} - - for rows.Next() { - pod, err := s.podFromScannable(rows) - if err != nil { - return nil, err - } - - pods = append(pods, pod) - } - - if err := rows.Err(); err != nil { - return nil, errors.Wrapf(err, "error retrieving pod rows") - } - - return pods, nil -} diff --git a/libpod/sql_state_internal.go b/libpod/sql_state_internal.go deleted file mode 100644 index 7729fb6ec..000000000 --- a/libpod/sql_state_internal.go +++ /dev/null @@ -1,1137 +0,0 @@ -package libpod - -import ( - "database/sql" - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/containernetworking/cni/pkg/types" - cnitypes "github.com/containernetworking/cni/pkg/types/current" - "github.com/containers/storage" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - // Use SQLite backend for sql package - _ "github.com/mattn/go-sqlite3" -) - -const ( - // Basic structure of a query to retrieve a container - // Just append optional WHERE clauses and end with a semicolon - // Contains trailing whitespace to ensure we can append without issue - containerQuery = `SELECT containers.*, - containerState.State, - containerState.ConfigPath, - containerState.RunDir, - containerState.MountPoint, - containerState.StartedTime, - containerState.FinishedTime, - containerState.ExitCode, - containerState.OomKilled, - containerState.Pid, - containerState.NetNSPath, - containerState.ExecSessions, - containerState.IPs, - containerState.Routes, - containerState.BindMounts - FROM containers - INNER JOIN - containerState ON containers.Id = containerState.Id ` - // ExistsQuery is a query to check if a pod exists - ExistsQuery = "SELECT 1 FROM pods WHERE Id=?;" -) - -// Checks that the DB configuration matches the runtime's configuration -func checkDB(db *sql.DB, r *Runtime) (err error) { - // Create a table to hold runtime information - // TODO: Include UID/GID mappings - const runtimeTable = ` - CREATE TABLE runtime( - Id INTEGER NOT NULL PRIMARY KEY, - SchemaVersion INTEGER NOT NULL, - StaticDir TEXT NOT NULL, - TmpDir TEXT NOT NULL, - RunRoot TEXT NOT NULL, - GraphRoot TEXT NOT NULL, - GraphDriverName TEXT NOT NULL, - CHECK (Id=0) - ); - ` - const fillRuntimeTable = `INSERT INTO runtime VALUES ( - ?, ?, ?, ?, ?, ?, ? - );` - - const selectRuntimeTable = `SELECT SchemaVersion, - StaticDir, - TmpDir, - RunRoot, - GraphRoot, - GraphDriverName - FROM runtime WHERE id=0;` - - const checkRuntimeExists = "SELECT name FROM sqlite_master WHERE type='table' AND name='runtime';" - - committed := false - - tx, err := 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 check runtime table: %v", err2) - } - } - - }() - - row := tx.QueryRow(checkRuntimeExists) - var table string - if err := row.Scan(&table); err != nil { - // There is no runtime table - // Create and populate the runtime table - if err == sql.ErrNoRows { - if _, err := tx.Exec(runtimeTable); err != nil { - return errors.Wrapf(err, "error creating runtime table in database") - } - - _, err := tx.Exec(fillRuntimeTable, - 0, - DBSchema, - r.config.StaticDir, - r.config.TmpDir, - r.config.StorageConfig.RunRoot, - r.config.StorageConfig.GraphRoot, - r.config.StorageConfig.GraphDriverName) - if err != nil { - return errors.Wrapf(err, "error populating runtime table in database") - } - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing runtime table transaction in database") - } - - return nil - } - - return errors.Wrapf(err, "error checking for presence of runtime table in database") - } - - // There is a runtime table - // Retrieve its contents - var ( - schemaVersion int - staticDir string - tmpDir string - runRoot string - graphRoot string - graphDriverName string - ) - - row = tx.QueryRow(selectRuntimeTable) - err = row.Scan( - &schemaVersion, - &staticDir, - &tmpDir, - &runRoot, - &graphRoot, - &graphDriverName) - if err != nil { - return errors.Wrapf(err, "error retrieving runtime information from database") - } - - // Compare the information in the database against our runtime config - if schemaVersion != DBSchema { - return errors.Wrapf(ErrDBBadConfig, "database schema version %d does not match our schema version %d", - schemaVersion, DBSchema) - } - if staticDir != r.config.StaticDir { - return errors.Wrapf(ErrDBBadConfig, "database static directory %s does not match our static directory %s", - staticDir, r.config.StaticDir) - } - if tmpDir != r.config.TmpDir { - return errors.Wrapf(ErrDBBadConfig, "database temp directory %s does not match our temp directory %s", - tmpDir, r.config.TmpDir) - } - if runRoot != r.config.StorageConfig.RunRoot { - return errors.Wrapf(ErrDBBadConfig, "database runroot directory %s does not match our runroot directory %s", - runRoot, r.config.StorageConfig.RunRoot) - } - if graphRoot != r.config.StorageConfig.GraphRoot { - return errors.Wrapf(ErrDBBadConfig, "database graph root directory %s does not match our graph root directory %s", - graphRoot, r.config.StorageConfig.GraphRoot) - } - if graphDriverName != r.config.StorageConfig.GraphDriverName { - return errors.Wrapf(ErrDBBadConfig, "database runroot directory %s does not match our runroot directory %s", - graphDriverName, r.config.StorageConfig.GraphDriverName) - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing runtime table transaction in database") - } - - return nil -} - -// Performs database setup including by not limited to initializing tables in -// the database -func prepareDB(db *sql.DB) (err error) { - // TODO schema migration might be necessary and should be handled here - // TODO maybe make a port mappings table instead of JSONing the array and storing it? - // TODO prepared statements for common queries for performance - - // Enable foreign keys in SQLite - if _, err := db.Exec("PRAGMA foreign_keys = ON;"); err != nil { - return errors.Wrapf(err, "error enabling foreign key support in database") - } - - // Create a table for holding container and pod names and IDs - const createRegistry = ` - CREATE TABLE IF NOT EXISTS registry( - Id TEXT NOT NULL PRIMARY KEY, - Name TEXT NOT NULL UNIQUE - ); - ` - - // Create a table for unchanging container data - const createCtr = ` - CREATE TABLE IF NOT EXISTS containers( - Id TEXT NOT NULL PRIMARY KEY, - Name TEXT NOT NULL UNIQUE, - Pod TEXT, - - RootfsImageID TEXT NOT NULL, - RootfsImageName TEXT NOT NULL, - ImageVolumes INTEGER NOT NULL, - ShmDir TEXT NOT NULL, - ShmSize INTEGER NOT NULL, - StaticDir TEXT NOT NULL, - Mounts TEXT NOT NULL, - LogPath TEXT NOT NULL, - - Privileged INTEGER NOT NULL, - ProcessLabel TEXT NOT NULL, - MountLabel TEXT NOT NULL, - User TEXT NOT NULL, - - IPCNsCtr TEXT, - MountNsCtr TEXT, - NetNsCtr TEXT, - PIDNsCtr TEXT, - UserNsCtr TEXT, - UTSNsCtr TEXT, - CgroupNsCtr TEXT, - - CreateNetNS INTEGER NOT NULL, - DNSServer TEXT NOT NULL, - DNSSearch TEXT NOT NULL, - DNSOption TEXT NOT NULL, - HostAdd TEXT NOT NULL, - - Stdin INTEGER NOT NULL, - LabelsJSON TEXT NOT NULL, - StopSignal INTEGER NOT NULL, - StopTimeout INTEGER NOT NULL, - CreatedTime TEXT NOT NULL, - CgroupParent TEXT NOT NULL, - - CHECK (ImageVolumes IN (0, 1)), - CHECK (SHMSize>=0), - CHECK (Privileged IN (0, 1)), - CHECK (CreateNetNS IN (0, 1)), - CHECK (Stdin IN (0, 1)), - CHECK (StopSignal>=0), - FOREIGN KEY (Id) REFERENCES registry(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (Id) REFERENCES containerState(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (Name) REFERENCES registry(Name) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (Pod) REFERENCES pods(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (IPCNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (MountNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (NetNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (PIDNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (UserNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (UTSNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (CgroupNsCtr) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED - ); - ` - - // Create a table for changing container state - const createCtrState = ` - CREATE TABLE IF NOT EXISTS containerState( - Id TEXT NOT NULL PRIMARY KEY, - State INTEGER NOT NULL, - ConfigPath TEXT NOT NULL, - RunDir TEXT NOT NULL, - Mountpoint TEXT NOT NULL, - StartedTime TEXT NUT NULL, - FinishedTime TEXT NOT NULL, - ExitCode INTEGER NOT NULL, - OomKilled INTEGER NOT NULL, - Pid INTEGER NOT NULL, - NetNSPath TEXT NOT NULL, - ExecSessions TEXT NOT NULL, - IPs TEXT NOT NULL, - Routes TEXT NOT NULL, - BindMounts TEXT NOT NULL, - - CHECK (State>0), - CHECK (OomKilled IN (0, 1)), - FOREIGN KEY (Id) REFERENCES registry(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (Id) REFERENCES containers(Id) DEFERRABLE INITIALLY DEFERRED - ); - ` - - // Create a table for pod config - const createPod = ` - CREATE TABLE IF NOT EXISTS pods( - Id TEXT NOT NULL PRIMARY KEY, - Name TEXT NOT NULL UNIQUE, - Labels TEXT NOT NULL, - FOREIGN KEY (Id) REFERENCES registry(Id) DEFERRABLE INITIALLY DEFERRED, - FOREIGN KEY (Name) REFERENCES registry(Name) DEFERRABLE INITIALLY DEFERRED - ); - ` - - committed := false - - // Create the tables - tx, err := 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 create tables: %v", err2) - } - } - - }() - - if _, err := tx.Exec(createRegistry); err != nil { - return errors.Wrapf(err, "error creating ID and Name registry in database") - } - if _, err := tx.Exec(createCtr); err != nil { - return errors.Wrapf(err, "error creating containers table in database") - } - if _, err := tx.Exec(createCtrState); err != nil { - return errors.Wrapf(err, "error creating container state table in database") - } - if _, err := tx.Exec(createPod); err != nil { - return errors.Wrapf(err, "error creating pods table in database") - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing table creation transaction in database") - } - - return nil -} - -// Check if given pod exists -// Internal-only version of hasPod -func (s *SQLState) podExists(id string) (bool, error) { - row := s.db.QueryRow(ExistsQuery, 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 pod %s", id) - } else if check != 1 { - return false, errors.Wrapf(ErrInternal, "check digit for podExists query incorrect") - } - - return true, nil -} - -// Check if given pod exists within a transaction -// Transaction-based version of podExists -func podExistsTx(id string, tx *sql.Tx) (bool, error) { - row := tx.QueryRow(ExistsQuery, 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 pod %s", id) - } else if check != 1 { - return false, errors.Wrapf(ErrInternal, "check digit for podExists query incorrect") - } - - return true, nil -} - -// Get filename for OCI spec on disk -func getSpecPath(specsDir, id string) string { - return filepath.Join(specsDir, id) -} - -// Get filename for container port mappings on disk -func getPortsPath(specsDir, id string) string { - return filepath.Join(specsDir, id+"_ports") -} - -// Convert a bool into SQL-readable format -func boolToSQL(b bool) int { - if b { - return 1 - } - - return 0 -} - -// Convert a null string from SQL-readable format -func stringFromNullString(s sql.NullString) string { - if s.Valid { - return s.String - } - return "" -} - -// Convert a string to a SQL nullable string -func stringToNullString(s string) sql.NullString { - if s == "" { - return sql.NullString{} - } - return sql.NullString{ - String: s, - Valid: true, - } -} - -// Convert a bool from SQL-readable format -func boolFromSQL(i int) bool { - return i != 0 -} - -// Convert a time.Time into SQL-readable format -func timeToSQL(t time.Time) string { - return t.Format(time.RFC3339Nano) -} - -// Convert a SQL-readable time back to a time.Time -func timeFromSQL(s string) (time.Time, error) { - return time.Parse(time.RFC3339Nano, s) -} - -// Interface to abstract sql.Rows and sql.Row so they can both be used -type scannable interface { - Scan(dest ...interface{}) error -} - -// Read a single container from a single row result in the database -func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { - var ( - id string - name string - pod sql.NullString - - rootfsImageID string - rootfsImageName string - imageVolumes int - shmDir string - shmSize int64 - staticDir string - mounts string - logPath string - - privileged int - processLabel string - mountLabel string - user string - - ipcNsCtrNullStr sql.NullString - mountNsCtrNullStr sql.NullString - netNsCtrNullStr sql.NullString - pidNsCtrNullStr sql.NullString - userNsCtrNullStr sql.NullString - utsNsCtrNullStr sql.NullString - cgroupNsCtrNullStr sql.NullString - - createNetNS int - dnsServerJSON string - dnsSearchJSON string - dnsOptionJSON string - hostAddJSON string - - stdin int - labelsJSON string - stopSignal uint - stopTimeout uint - createdTimeString string - cgroupParent string - - state int - configPath string - runDir string - mountpoint string - startedTimeString string - finishedTimeString string - exitCode int32 - oomKilled int - pid int - netNSPath string - execSessions string - ipsJSON string - routesJSON string - bindMountsJSON string - ) - - err := row.Scan( - &id, - &name, - &pod, - - &rootfsImageID, - &rootfsImageName, - &imageVolumes, - &shmDir, - &shmSize, - &staticDir, - &mounts, - &logPath, - - &privileged, - &processLabel, - &mountLabel, - &user, - - &ipcNsCtrNullStr, - &mountNsCtrNullStr, - &netNsCtrNullStr, - &pidNsCtrNullStr, - &userNsCtrNullStr, - &utsNsCtrNullStr, - &cgroupNsCtrNullStr, - - &createNetNS, - &dnsServerJSON, - &dnsSearchJSON, - &dnsOptionJSON, - &hostAddJSON, - - &stdin, - &labelsJSON, - &stopSignal, - &stopTimeout, - &createdTimeString, - &cgroupParent, - - &state, - &configPath, - &runDir, - &mountpoint, - &startedTimeString, - &finishedTimeString, - &exitCode, - &oomKilled, - &pid, - &netNSPath, - &execSessions, - &ipsJSON, - &routesJSON, - &bindMountsJSON) - if err != nil { - if err == sql.ErrNoRows { - return nil, ErrNoSuchCtr - } - - return nil, errors.Wrapf(err, "error parsing database row into container") - } - - ctr := new(Container) - ctr.config = new(ContainerConfig) - ctr.state = new(containerState) - - ctr.config.ID = id - ctr.config.Name = name - ctr.config.Pod = stringFromNullString(pod) - - ctr.config.RootfsImageID = rootfsImageID - ctr.config.RootfsImageName = rootfsImageName - ctr.config.ImageVolumes = boolFromSQL(imageVolumes) - ctr.config.ShmDir = shmDir - ctr.config.ShmSize = shmSize - ctr.config.StaticDir = staticDir - ctr.config.LogPath = logPath - - ctr.config.Privileged = boolFromSQL(privileged) - ctr.config.ProcessLabel = processLabel - ctr.config.MountLabel = mountLabel - ctr.config.User = user - - ctr.config.IPCNsCtr = stringFromNullString(ipcNsCtrNullStr) - ctr.config.MountNsCtr = stringFromNullString(mountNsCtrNullStr) - ctr.config.NetNsCtr = stringFromNullString(netNsCtrNullStr) - ctr.config.PIDNsCtr = stringFromNullString(pidNsCtrNullStr) - ctr.config.UserNsCtr = stringFromNullString(userNsCtrNullStr) - ctr.config.UTSNsCtr = stringFromNullString(utsNsCtrNullStr) - ctr.config.CgroupNsCtr = stringFromNullString(cgroupNsCtrNullStr) - - ctr.config.CreateNetNS = boolFromSQL(createNetNS) - - ctr.config.Stdin = boolFromSQL(stdin) - ctr.config.StopSignal = stopSignal - ctr.config.StopTimeout = stopTimeout - ctr.config.CgroupParent = cgroupParent - - ctr.state.State = ContainerStatus(state) - ctr.state.ConfigPath = configPath - ctr.state.RunDir = runDir - ctr.state.Mountpoint = mountpoint - ctr.state.ExitCode = exitCode - ctr.state.OOMKilled = boolFromSQL(oomKilled) - ctr.state.PID = pid - - // TODO should we store this in the database separately instead? - if ctr.state.Mountpoint != "" { - ctr.state.Mounted = true - } - - if err := json.Unmarshal([]byte(mounts), &ctr.config.Mounts); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s mounts JSON", id) - } - - if err := json.Unmarshal([]byte(dnsServerJSON), &ctr.config.DNSServer); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id) - } - - if err := json.Unmarshal([]byte(dnsSearchJSON), &ctr.config.DNSSearch); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s DNS search JSON", id) - } - - if err := json.Unmarshal([]byte(dnsOptionJSON), &ctr.config.DNSOption); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s DNS option JSON", id) - } - - if err := json.Unmarshal([]byte(hostAddJSON), &ctr.config.HostAdd); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s DNS server JSON", id) - } - - ctr.state.ExecSessions = make(map[string]*ExecSession) - if err := json.Unmarshal([]byte(execSessions), &ctr.state.ExecSessions); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s exec sessions JSON", id) - } - - ips := []*cnitypes.IPConfig{} - if err := json.Unmarshal([]byte(ipsJSON), &ips); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s IP addresses JSON", id) - } - if len(ips) > 0 { - ctr.state.IPs = ips - } - - routes := []*types.Route{} - if err := json.Unmarshal([]byte(routesJSON), &routes); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s routes JSON", id) - } - if len(routes) > 0 { - ctr.state.Routes = routes - } - - bindMounts := make(map[string]string) - if err := json.Unmarshal([]byte(bindMountsJSON), &bindMounts); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s bind mounts JSON", id) - } - ctr.state.BindMounts = bindMounts - - labels := make(map[string]string) - if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s labels JSON", id) - } - ctr.config.Labels = labels - - createdTime, err := timeFromSQL(createdTimeString) - if err != nil { - return nil, errors.Wrapf(err, "error parsing container %s created time", id) - } - ctr.config.CreatedTime = createdTime - - startedTime, err := timeFromSQL(startedTimeString) - if err != nil { - return nil, errors.Wrapf(err, "error parsing container %s started time", id) - } - ctr.state.StartedTime = startedTime - - finishedTime, err := timeFromSQL(finishedTimeString) - if err != nil { - return nil, errors.Wrapf(err, "error parsing container %s finished time", id) - } - ctr.state.FinishedTime = finishedTime - - // Join the network namespace, if there is one - if netNSPath != "" { - netNS, err := joinNetNS(netNSPath) - if err != nil { - return nil, errors.Wrapf(err, "error joining network namespace for container %s", id) - } - ctr.state.NetNS = netNS - } - - ctr.valid = true - ctr.runtime = s.runtime - - // Open and set the lockfile - lockPath := filepath.Join(s.lockDir, id) - lock, err := storage.GetLockfile(lockPath) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving lockfile for container %s", id) - } - ctr.lock = lock - - // Retrieve the spec from disk - ociSpec := new(spec.Spec) - specPath := getSpecPath(s.specsDir, id) - fileContents, err := ioutil.ReadFile(specPath) - if err != nil { - return nil, errors.Wrapf(err, "error reading container %s OCI spec", id) - } - if err := json.Unmarshal(fileContents, ociSpec); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s OCI spec", id) - } - ctr.config.Spec = ociSpec - - // Retrieve the ports from disk - // They may not exist - if they don't, this container just doesn't have ports - portPath := getPortsPath(s.specsDir, id) - if _, err = os.Stat(portPath); err != nil { - if !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "error stating container %s JSON ports", id) - } - } - if err == nil { - // The file exists, read it - fileContents, err := ioutil.ReadFile(portPath) - if err != nil { - return nil, errors.Wrapf(err, "error reading container %s JSON ports", id) - } - if err := json.Unmarshal(fileContents, &ctr.config.PortMappings); err != nil { - return nil, errors.Wrapf(err, "error parsing container %s JSON ports", id) - } - } - - return ctr, nil -} - -// Read a single pod from a single row result in the database -func (s *SQLState) podFromScannable(row scannable) (*Pod, error) { - var ( - id string - name string - labelsJSON string - ) - - err := row.Scan(&id, &name, &labelsJSON) - if err != nil { - if err == sql.ErrNoRows { - return nil, ErrNoSuchPod - } - - return nil, errors.Wrapf(err, "error parsing database row into pod") - } - - pod := new(Pod) - pod.config = new(PodConfig) - pod.config.ID = id - pod.config.Name = name - pod.runtime = s.runtime - - // Decode labels JSON - podLabels := make(map[string]string) - if err := json.Unmarshal([]byte(labelsJSON), &podLabels); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling pod %s labels JSON", id) - } - pod.config.Labels = podLabels - - // Retrieve pod lock - // Open and set the lockfile - lockPath := filepath.Join(s.lockDir, id) - lock, err := storage.GetLockfile(lockPath) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving lockfile for pod %s", id) - } - pod.lock = lock - - pod.valid = true - - return pod, nil -} - -// Internal function for adding containers -func (s *SQLState) addContainer(ctr *Container, pod *Pod) (err error) { - const ( - addCtr = `INSERT INTO containers VALUES ( - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ? - );` - addCtrState = `INSERT INTO containerState VALUES ( - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, - ?, ?, ?, ?, ? - );` - addRegistry = "INSERT INTO registry VALUES (?, ?);" - checkCtrInPod = "SELECT 1 FROM containers WHERE Id=? AND Pod=?;" - ) - - if !s.valid { - return ErrDBClosed - } - - depCtrs := ctr.Dependencies() - - mounts, err := json.Marshal(ctr.config.Mounts) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s mounts to JSON", ctr.ID()) - } - - dnsServerJSON, err := json.Marshal(ctr.config.DNSServer) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s DNS servers to JSON", ctr.ID()) - } - - dnsSearchJSON, err := json.Marshal(ctr.config.DNSSearch) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s DNS search domains to JSON", ctr.ID()) - } - - dnsOptionJSON, err := json.Marshal(ctr.config.DNSOption) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s DNS options to JSON", ctr.ID()) - } - - hostAddJSON, err := json.Marshal(ctr.config.HostAdd) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s hosts to JSON", ctr.ID()) - } - - labelsJSON, err := json.Marshal(ctr.config.Labels) - if err != nil { - return errors.Wrapf(err, "error marshaling container %s labels to JSON", ctr.ID()) - } - - execSessionsJSON, err := json.Marshal(ctr.state.ExecSessions) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s exec sessions to JSON", ctr.ID()) - } - - ipsJSON, err := json.Marshal(ctr.state.IPs) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s IPs to JSON", ctr.ID()) - } - - routesJSON, err := json.Marshal(ctr.state.Routes) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s routes to JSON", ctr.ID()) - } - - bindMountsJSON, err := json.Marshal(ctr.state.BindMounts) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s bind mounts to JSON", ctr.ID()) - } - - netNSPath := "" - if ctr.state.NetNS != nil { - netNSPath = ctr.state.NetNS.Path() - } - - specJSON, err := json.Marshal(ctr.config.Spec) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s spec to JSON", ctr.ID()) - } - - portsJSON := []byte{} - if len(ctr.config.PortMappings) > 0 { - portsJSON, err = json.Marshal(&ctr.config.PortMappings) - if err != nil { - return errors.Wrapf(err, "error marshalling container %s port mappings to JSON", ctr.ID()) - } - } - - 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) - } - } - }() - - // First check if pod exists, if one is given - if pod != nil { - exists, err := podExistsTx(pod.ID(), tx) - if err != nil { - return err - } - - if !exists { - pod.valid = false - return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist in state, cannot add container to it", pod.ID()) - } - - // We also need to check if our dependencies are in the pod - for _, depID := range depCtrs { - row := tx.QueryRow(checkCtrInPod, depID, pod.ID()) - var check int - err := row.Scan(&check) - if err != nil { - if err == sql.ErrNoRows { - return errors.Wrapf(ErrInvalidArg, "container %s depends on container %s but it is not in pod %s", ctr.ID(), depID, pod.ID()) - } - return errors.Wrapf(err, "error querying for existence of container %s", depID) - } else if check != 1 { - return errors.Wrapf(ErrInternal, "check digit for checkCtrInPod query incorrect") - } - } - } - - // Add container to registry - if _, err := tx.Exec(addRegistry, ctr.ID(), ctr.Name()); err != nil { - return errors.Wrapf(err, "error adding container %s to name/ID registry", ctr.ID()) - } - - // Add static container information - _, err = tx.Exec(addCtr, - ctr.ID(), - ctr.Name(), - stringToNullString(ctr.PodID()), - - ctr.config.RootfsImageID, - ctr.config.RootfsImageName, - boolToSQL(ctr.config.ImageVolumes), - ctr.config.ShmDir, - ctr.config.ShmSize, - ctr.config.StaticDir, - string(mounts), - ctr.config.LogPath, - - boolToSQL(ctr.config.Privileged), - ctr.config.ProcessLabel, - ctr.config.MountLabel, - ctr.config.User, - - stringToNullString(ctr.config.IPCNsCtr), - stringToNullString(ctr.config.MountNsCtr), - stringToNullString(ctr.config.NetNsCtr), - stringToNullString(ctr.config.PIDNsCtr), - stringToNullString(ctr.config.UserNsCtr), - stringToNullString(ctr.config.UTSNsCtr), - stringToNullString(ctr.config.CgroupNsCtr), - - boolToSQL(ctr.config.CreateNetNS), - string(dnsServerJSON), - string(dnsSearchJSON), - string(dnsOptionJSON), - string(hostAddJSON), - - boolToSQL(ctr.config.Stdin), - string(labelsJSON), - ctr.config.StopSignal, - ctr.config.StopTimeout, - timeToSQL(ctr.config.CreatedTime), - ctr.config.CgroupParent) - 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, - boolToSQL(ctr.state.OOMKilled), - ctr.state.PID, - netNSPath, - execSessionsJSON, - ipsJSON, - routesJSON, - bindMountsJSON) - if err != nil { - return errors.Wrapf(err, "error adding container %s state to database", ctr.ID()) - } - - // Save the container's runtime spec to disk - 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) - } - } - }() - - // If the container has port mappings, save them to disk - if len(ctr.config.PortMappings) > 0 { - portPath := getPortsPath(s.specsDir, ctr.ID()) - if err := ioutil.WriteFile(portPath, portsJSON, 0750); err != nil { - return errors.Wrapf(err, "error saving container %s port JSON to disk", ctr.ID()) - } - defer func() { - if err != nil { - if err2 := os.Remove(portPath); err2 != nil { - logrus.Errorf("Error removing container %s JSON ports from state: %v", ctr.ID(), err2) - } - } - }() - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID()) - } - - return nil -} - -// Internal functions for removing containers -func (s *SQLState) removeContainer(ctr *Container, pod *Pod) (err error) { - const ( - removeCtr = "DELETE FROM containers WHERE Id=?;" - removeState = "DELETE FROM containerState WHERE Id=?;" - removeRegistry = "DELETE FROM registry WHERE Id=?;" - existsInPod = "SELECT 1 FROM containers WHERE Id=? AND Pod=?;" - ctrExists = "SELECT 1 FROM containers WHERE Id=?;" - ) - - 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) - } - } - }() - - if pod != nil { - // Check to see if the pod exists - exists, err := podExistsTx(pod.ID(), tx) - if err != nil { - return err - } - - if !exists { - pod.valid = false - return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist in state, cannot add container to it", pod.ID()) - } - - var check int - - // Check to see if the container exists - row := tx.QueryRow(ctrExists, ctr.ID()) - err = row.Scan(&check) - if err != nil { - if err == sql.ErrNoRows { - ctr.valid = false - return errors.Wrapf(ErrNoSuchCtr, "container %s does not exist", ctr.ID()) - } - - return errors.Wrapf(err, "error querying database for container %s existence", ctr.ID()) - } else if check != 1 { - return errors.Wrapf(ErrInternal, "check digit for ctr exists query incorrect") - } - - // Check to see if the container is in the pod - row = tx.QueryRow(existsInPod, ctr.ID(), pod.ID()) - err = row.Scan(&check) - if err != nil { - if err == sql.ErrNoRows { - return errors.Wrapf(ErrNoSuchCtr, "container %s is not in pod %s", ctr.ID(), pod.ID()) - } - - return errors.Wrapf(err, "error querying database for container %s existence", ctr.ID()) - } else if check != 1 { - return errors.Wrapf(ErrInternal, "check digit for ctr exists in pod query incorrect") - } - } - - // 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 { - ctr.valid = false - return ErrNoSuchCtr - } - - if _, err := tx.Exec(removeState, ctr.ID()); err != nil { - return errors.Wrapf(err, "error removing container %s from state table", ctr.ID()) - } - - // Remove registry last of all - // So we know the container did exist - if _, err := tx.Exec(removeRegistry, ctr.ID()); err != nil { - return errors.Wrapf(err, "error removing container %s from name/ID registry", ctr.ID()) - } - - committed = true - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to remove container %s", ctr.ID()) - } - - // 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()) - } - - // Remove containers ports JSON from disk - // May not exist, so ignore os.IsNotExist - portsPath := getPortsPath(s.specsDir, ctr.ID()) - if err := os.Remove(portsPath); err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing JSON ports from state for container %s", ctr.ID()) - } - } - - ctr.valid = false - - return nil -} diff --git a/libpod/state_test.go b/libpod/state_test.go index 1b9a04d75..634280c99 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -24,7 +24,6 @@ const ( var ( testedStates = map[string]emptyStateFunc{ - "sql": getEmptySQLState, "in-memory": getEmptyInMemoryState, "boltdb": getEmptyBoltState, } @@ -79,35 +78,6 @@ func getEmptyInMemoryState() (s State, p string, p2 string, err error) { return state, tmpDir, tmpDir, nil } -// Get an empty SQL state for use in tests -// An empty Runtime is provided -func getEmptySQLState() (s State, p string, p2 string, err error) { - tmpDir, err := ioutil.TempDir("", tmpDirPrefix) - if err != nil { - return nil, "", "", err - } - defer func() { - if err != nil { - os.RemoveAll(tmpDir) - } - }() - - dbPath := filepath.Join(tmpDir, "db.sql") - specsDir := filepath.Join(tmpDir, "specs") - lockDir := filepath.Join(tmpDir, "locks") - - runtime := new(Runtime) - runtime.config = new(RuntimeConfig) - runtime.config.StorageConfig = storage.StoreOptions{} - - state, err := NewSQLState(dbPath, specsDir, lockDir, runtime) - if err != nil { - return nil, "", "", err - } - - return state, tmpDir, lockDir, nil -} - func runForAllStates(t *testing.T, testFunc func(*testing.T, State, string)) { for stateName, stateFunc := range testedStates { state, path, lockPath, err := stateFunc() |