Add structure for new exec session tracking to DB

As part of the rework of exec sessions, we need to address them
independently of containers. In the new API, we need to be able
to fetch them by their ID, regardless of what container they are
associated with. Unfortunately, our existing exec sessions are
tied to individual containers; there's no way to tell what
container a session belongs to and retrieve it without getting
every exec session for every container.

This adds a pointer to the container an exec session is
associated with to the database. The sessions themselves are
still stored in the container.

Exec-related APIs have been restructured to work with the new
database representation. The originally monolithic API has been
split into a number of smaller calls to allow more fine-grained
control of lifecycle. Support for legacy exec sessions has been
retained, but in a deprecated fashion; we should remove this in
a few releases.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:
Matthew Heon
2020-02-04 16:56:07 -05:00
parent f138405b46
commit 118e78c5d6
19 changed files with 1430 additions and 489 deletions

View File

@ -142,92 +142,6 @@ func (c *Container) exitFilePath() (string, error) {
return c.ociRuntime.ExitFilePath(c)
}
// create a bundle path and associated files for an exec session
func (c *Container) createExecBundle(sessionID string) (err error) {
bundlePath := c.execBundlePath(sessionID)
if createErr := os.MkdirAll(bundlePath, execDirPermission); createErr != nil {
return createErr
}
defer func() {
if err != nil {
if err2 := os.RemoveAll(bundlePath); err != nil {
logrus.Warnf("error removing exec bundle after creation caused another error: %v", err2)
}
}
}()
if err2 := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err2 != nil {
// The directory is allowed to exist
if !os.IsExist(err2) {
err = errors.Wrapf(err2, "error creating OCI runtime exit file path %s", c.execExitFileDir(sessionID))
}
}
return
}
// cleanup an exec session after its done
func (c *Container) cleanupExecBundle(sessionID string) error {
if err := os.RemoveAll(c.execBundlePath(sessionID)); err != nil && !os.IsNotExist(err) {
return err
}
return c.ociRuntime.ExecContainerCleanup(c, sessionID)
}
// the path to a containers exec session bundle
func (c *Container) execBundlePath(sessionID string) string {
return filepath.Join(c.bundlePath(), sessionID)
}
// Get PID file path for a container's exec session
func (c *Container) execPidPath(sessionID string) string {
return filepath.Join(c.execBundlePath(sessionID), "exec_pid")
}
// the log path for an exec session
func (c *Container) execLogPath(sessionID string) string {
return filepath.Join(c.execBundlePath(sessionID), "exec_log")
}
// the socket conmon creates for an exec session
func (c *Container) execAttachSocketPath(sessionID string) (string, error) {
return c.ociRuntime.ExecAttachSocketPath(c, sessionID)
}
// execExitFileDir gets the path to the container's exit file
func (c *Container) execExitFileDir(sessionID string) string {
return filepath.Join(c.execBundlePath(sessionID), "exit")
}
// execOCILog returns the file path for the exec sessions oci log
func (c *Container) execOCILog(sessionID string) string {
if !c.ociRuntime.SupportsJSONErrors() {
return ""
}
return filepath.Join(c.execBundlePath(sessionID), "oci-log")
}
// readExecExitCode reads the exit file for an exec session and returns
// the exit code
func (c *Container) readExecExitCode(sessionID string) (int, error) {
exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
chWait := make(chan error)
defer close(chWait)
_, err := WaitForFile(exitFile, chWait, time.Second*5)
if err != nil {
return -1, err
}
ec, err := ioutil.ReadFile(exitFile)
if err != nil {
return -1, err
}
ecInt, err := strconv.Atoi(string(ec))
if err != nil {
return -1, err
}
return ecInt, nil
}
// Wait for the container's exit file to appear.
// When it does, update our state based on it.
func (c *Container) waitForExitFileAndSync() error {
@ -568,6 +482,7 @@ func resetState(state *ContainerState) error {
state.State = define.ContainerStateConfigured
}
state.ExecSessions = make(map[string]*ExecSession)
state.LegacyExecSessions = nil
state.NetworkStatus = nil
state.BindMounts = make(map[string]string)
state.StoppedByUser = false
@ -1814,12 +1729,12 @@ func (c *Container) checkReadyForRemoval() error {
return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it is %s - running or paused containers cannot be removed without force", c.ID(), c.state.State.String())
}
// Reap exec sessions
if err := c.reapExecSessions(); err != nil {
// Check exec sessions
sessions, err := c.getActiveExecSessions()
if err != nil {
return err
}
if len(c.state.ExecSessions) != 0 {
if len(sessions) != 0 {
return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it has active exec sessions", c.ID())
}
@ -1926,41 +1841,6 @@ func (c *Container) checkExitFile() error {
return c.handleExitFile(exitFile, info)
}
// Reap dead exec sessions
func (c *Container) reapExecSessions() error {
// Instead of saving once per iteration, use a defer to do it once at
// the end.
var lastErr error
needSave := false
for id := range c.state.ExecSessions {
alive, err := c.ociRuntime.ExecUpdateStatus(c, id)
if err != nil {
if lastErr != nil {
logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr)
}
lastErr = err
continue
}
if !alive {
// Clean up lingering files and remove the exec session
if err := c.ociRuntime.ExecContainerCleanup(c, id); err != nil {
return errors.Wrapf(err, "error cleaning up container %s exec session %s files", c.ID(), id)
}
delete(c.state.ExecSessions, id)
needSave = true
}
}
if needSave {
if err := c.save(); err != nil {
if lastErr != nil {
logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr)
}
lastErr = err
}
}
return lastErr
}
func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool {
if c.config.Spec == nil || c.config.Spec.Linux == nil {
return false