diff options
author | Matthew Heon <matthew.heon@gmail.com> | 2018-01-22 10:55:33 -0500 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-02-09 15:01:34 +0000 |
commit | cfd6da22df9c580b8e6b6056a3ad0fbc1da71335 (patch) | |
tree | 412213afb7a82c769b1d9175b90e7f36350b7e92 | |
parent | 6214be07c2bf82f90361f058ab0c7178b634ecf9 (diff) | |
download | podman-cfd6da22df9c580b8e6b6056a3ad0fbc1da71335.tar.gz podman-cfd6da22df9c580b8e6b6056a3ad0fbc1da71335.tar.bz2 podman-cfd6da22df9c580b8e6b6056a3ad0fbc1da71335.zip |
Implement pod operations in SQL state
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Closes: #268
Approved by: rhatdan
-rw-r--r-- | libpod/sql_state.go | 619 | ||||
-rw-r--r-- | libpod/sql_state_internal.go | 342 |
2 files changed, 655 insertions, 306 deletions
diff --git a/libpod/sql_state.go b/libpod/sql_state.go index 5c843ad6f..373f1250d 100644 --- a/libpod/sql_state.go +++ b/libpod/sql_state.go @@ -3,7 +3,6 @@ package libpod import ( "database/sql" "encoding/json" - "io/ioutil" "os" "github.com/pkg/errors" @@ -148,23 +147,7 @@ func (s *SQLState) Refresh() (err error) { // 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, - containerState.OomKilled, - containerState.Pid, - containerState.NetNSPath, - containerState.IPAddress, - containerState.SubnetMask - FROM containers - INNER JOIN - containerState ON containers.Id = containerState.Id - WHERE containers.Id=?;` + const query = containerQuery + "WHERE containers.Id=?;" if id == "" { return nil, ErrEmptyID @@ -186,23 +169,7 @@ func (s *SQLState) Container(id string) (*Container, error) { // 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, - containerState.OomKilled, - containerState.Pid, - containerState.NetNSPath, - containerState.IPAddress, - containerState.SubnetMask - FROM containers - INNER JOIN - containerState ON containers.Id = containerState.Id - WHERE (containers.Id LIKE ?) OR containers.Name=?;` + const query = containerQuery + "WHERE (containers.Id LIKE ?) OR containers.Name=?;" if idOrName == "" { return nil, ErrEmptyID @@ -277,189 +244,15 @@ func (s *SQLState) HasContainer(id string) (bool, error) { // 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 } - 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()) - } - - 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()) - } - } - - 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(), - stringToNullString(ctr.PodID()), - - ctr.config.RootfsImageID, - ctr.config.RootfsImageName, - boolToSQL(ctr.config.ImageVolumes), - boolToSQL(ctr.config.ReadOnly), - ctr.config.ShmDir, - ctr.config.ShmSize, - ctr.config.StaticDir, - string(mounts), - ctr.LogPath(), - - boolToSQL(ctr.config.Privileged), - boolToSQL(ctr.config.NoNewPrivs), - 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, - ctr.state.IPAddress, - ctr.state.SubnetMask) - if err != nil { - return errors.Wrapf(err, "error adding container %s state to database", ctr.ID()) + if ctr.config.Pod != "" { + return errors.Wrapf(ErrPodExists, "cannot add container that belongs to a pod, use AddContainerToPod instead") } - // 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) - } - } - }() - } - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "error committing transaction to add container %s", ctr.ID()) - } - - return nil + return s.addContainer(ctr) } // UpdateContainer updates a container's state from the database @@ -709,93 +502,18 @@ func (s *SQLState) ContainerInUse(ctr *Container) ([]string, error) { return ids, nil } -// RemoveContainer removes the container from the state +// RemoveContainer removes the given container from the state func (s *SQLState) RemoveContainer(ctr *Container) error { - const ( - removeCtr = "DELETE FROM containers WHERE Id=?;" - removeState = "DELETE FROM containerState 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) - } - } - }() - - // 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 ctr.config.Pod != "" { + return errors.Wrapf(ErrPodExists, "container %s belongs to a pod, use RemoveContainerFromPod", ctr.ID()) } - 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()) - } - - // 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 + return s.removeContainer(ctr) } // 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, - containerState.OomKilled, - containerState.Pid, - containerState.NetNSPath, - containerState.IPAddress, - containerState.SubnetMask - FROM containers - INNER JOIN - containerState ON containers.Id = containerState.Id - ORDER BY containers.CreatedTime DESC;` + const query = containerQuery + ";" if !s.valid { return nil, ErrDBClosed @@ -826,59 +544,352 @@ func (s *SQLState) AllContainers() ([]*Container, error) { // Pod retrieves a pod by its full ID func (s *SQLState) Pod(id string) (*Pod, error) { - return nil, ErrNotImplemented + 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) { - return nil, ErrNotImplemented + 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) { - return false, ErrNotImplemented + 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) { - return false, ErrNotImplemented + 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) { - return nil, ErrNotImplemented + 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) { - return nil, ErrNotImplemented + 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) error { - return ErrNotImplemented +func (s *SQLState) AddPod(pod *Pod) (err error) { + const query = "INSERT INTO pods VALUES (?, ?, ?);" + + if !s.valid { + return ErrDBClosed + } + + if !pod.valid { + return ErrPodRemoved + } + + labelsJSON, err := json.Marshal(pod.labels) + if err != nil { + return errors.Wrapf(err, "error marshaling pod %s labels to JSON", pod.ID()) + } + + 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 pod %s: %v", pod.ID(), err2) + } + } + }() + + _, err = tx.Exec(query, pod.ID(), pod.Name(), string(labelsJSON)) + if err != nil { + return errors.Wrapf(err, "error adding pod %s to database", pod.ID()) + } + + 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) error { - return ErrNotImplemented + const query = "DELETE FROM pods WHERE ID=?;" + + if !s.valid { + return ErrDBClosed + } + + 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 remove pod %s: %v", pod.ID(), err2) + } + } + }() + + // Check rows acted on for the first transaction, verify we actually removed something + result, err := tx.Exec(query, pod.ID()) + if err != nil { + return errors.Wrapf(err, "error removing pod %s from containers 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 { + return ErrNoSuchPod + } + + if err := tx.Commit(); err != nil { + return errors.Wrapf(err, "error committing transaction to remove pod %s", pod.ID()) + } + + return nil } // AddContainerToPod adds a container to the given pod func (s *SQLState) AddContainerToPod(pod *Pod, ctr *Container) error { - return ErrNotImplemented + 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) } // RemoveContainerFromPod removes a container from the given pod func (s *SQLState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { - return ErrNotImplemented + 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) } // AllPods retrieves all pods presently in the state func (s *SQLState) AllPods() ([]*Pod, error) { - return nil, ErrNotImplemented + 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 index 6523bb589..271dff474 100644 --- a/libpod/sql_state_internal.go +++ b/libpod/sql_state_internal.go @@ -17,6 +17,28 @@ import ( _ "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.IPAddress, + containerState.SubnetMask + FROM containers + INNER JOIN + containerState ON containers.Id = containerState.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 @@ -216,7 +238,7 @@ func prepareDB(db *sql.DB) (err error) { CHECK (Stdin IN (0, 1)), CHECK (StopSignal>=0), FOREIGN KEY (Id) REFERENCES containerState(Id) DEFERRABLE INITIALLY DEFERRED - FOREIGN KEY (Pod) REFERENCES pod(Id) 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, @@ -252,7 +274,7 @@ func prepareDB(db *sql.DB) (err error) { // Create a table for pod config const createPod = ` - CREATE TABLE IF NOT EXISTS pod( + CREATE TABLE IF NOT EXISTS pods( Id TEXT NOT NULL PRIMARY KEY, Name TEXT NOT NULL UNIQUE, Labels TEXT NOT NULL @@ -290,6 +312,29 @@ func prepareDB(db *sql.DB) (err error) { return nil } +// Check if given pod exists +// Internal-only version of hasPod +func (s *SQLState) podExists(id string) (bool, error) { + const query = "SELECT 1 FROM pods WHERE Id=?;" + + 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 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) @@ -619,3 +664,296 @@ func (s *SQLState) ctrFromScannable(row scannable) (*Container, error) { 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.id = id + pod.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.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) (err error) { + const ( + addCtr = `INSERT INTO containers VALUES ( + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ? + );` + addCtrState = `INSERT INTO containerState VALUES ( + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ? + );` + ) + + if !s.valid { + return ErrDBClosed + } + + 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()) + } + + 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()) + } + } + + 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(), + stringToNullString(ctr.PodID()), + + ctr.config.RootfsImageID, + ctr.config.RootfsImageName, + boolToSQL(ctr.config.ImageVolumes), + boolToSQL(ctr.config.ReadOnly), + ctr.config.ShmDir, + ctr.config.ShmSize, + ctr.config.StaticDir, + string(mounts), + ctr.config.LogPath, + + boolToSQL(ctr.config.Privileged), + boolToSQL(ctr.config.NoNewPrivs), + 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, + ctr.state.IPAddress, + ctr.state.SubnetMask) + 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) + } + } + }() + } + + 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) error { + const ( + removeCtr = "DELETE FROM containers WHERE Id=?;" + removeState = "DELETE FROM containerState 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) + } + } + }() + + // 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()) + } + + // 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 +} |