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 /libpod/sql_state_internal.go | |
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
Diffstat (limited to 'libpod/sql_state_internal.go')
-rw-r--r-- | libpod/sql_state_internal.go | 342 |
1 files changed, 340 insertions, 2 deletions
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 +} |