github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/container/state.go (about)

     1  package container
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/go-units"
    13  )
    14  
    15  // State holds the current container state, and has methods to get and
    16  // set the state. Container has an embed, which allows all of the
    17  // functions defined against State to run against Container.
    18  type State struct {
    19  	sync.Mutex
    20  	// Note that `Running` and `Paused` are not mutually exclusive:
    21  	// When pausing a container (on Linux), the cgroups freezer is used to suspend
    22  	// all processes in the container. Freezing the process requires the process to
    23  	// be running. As a result, paused containers are both `Running` _and_ `Paused`.
    24  	Running           bool
    25  	Paused            bool
    26  	Restarting        bool
    27  	OOMKilled         bool
    28  	RemovalInProgress bool // Not need for this to be persistent on disk.
    29  	Dead              bool
    30  	Pid               int
    31  	ExitCodeValue     int    `json:"ExitCode"`
    32  	ErrorMsg          string `json:"Error"` // contains last known error during container start or remove
    33  	StartedAt         time.Time
    34  	FinishedAt        time.Time
    35  	Health            *Health
    36  
    37  	waitStop   chan struct{}
    38  	waitRemove chan struct{}
    39  }
    40  
    41  // StateStatus is used to return container wait results.
    42  // Implements exec.ExitCode interface.
    43  // This type is needed as State include a sync.Mutex field which make
    44  // copying it unsafe.
    45  type StateStatus struct {
    46  	exitCode int
    47  	err      error
    48  }
    49  
    50  // ExitCode returns current exitcode for the state.
    51  func (s StateStatus) ExitCode() int {
    52  	return s.exitCode
    53  }
    54  
    55  // Err returns current error for the state. Returns nil if the container had
    56  // exited on its own.
    57  func (s StateStatus) Err() error {
    58  	return s.err
    59  }
    60  
    61  // NewState creates a default state object with a fresh channel for state changes.
    62  func NewState() *State {
    63  	return &State{
    64  		waitStop:   make(chan struct{}),
    65  		waitRemove: make(chan struct{}),
    66  	}
    67  }
    68  
    69  // String returns a human-readable description of the state
    70  func (s *State) String() string {
    71  	if s.Running {
    72  		if s.Paused {
    73  			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
    74  		}
    75  		if s.Restarting {
    76  			return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
    77  		}
    78  
    79  		if h := s.Health; h != nil {
    80  			return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String())
    81  		}
    82  
    83  		return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
    84  	}
    85  
    86  	if s.RemovalInProgress {
    87  		return "Removal In Progress"
    88  	}
    89  
    90  	if s.Dead {
    91  		return "Dead"
    92  	}
    93  
    94  	if s.StartedAt.IsZero() {
    95  		return "Created"
    96  	}
    97  
    98  	if s.FinishedAt.IsZero() {
    99  		return ""
   100  	}
   101  
   102  	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
   103  }
   104  
   105  // HealthString returns a single string to describe health status.
   106  func (s *State) HealthString() string {
   107  	if s.Health == nil {
   108  		return types.NoHealthcheck
   109  	}
   110  
   111  	return s.Health.String()
   112  }
   113  
   114  // IsValidHealthString checks if the provided string is a valid container health status or not.
   115  func IsValidHealthString(s string) bool {
   116  	return s == types.Starting ||
   117  		s == types.Healthy ||
   118  		s == types.Unhealthy ||
   119  		s == types.NoHealthcheck
   120  }
   121  
   122  // StateString returns a single string to describe state
   123  func (s *State) StateString() string {
   124  	if s.Running {
   125  		if s.Paused {
   126  			return "paused"
   127  		}
   128  		if s.Restarting {
   129  			return "restarting"
   130  		}
   131  		return "running"
   132  	}
   133  
   134  	if s.RemovalInProgress {
   135  		return "removing"
   136  	}
   137  
   138  	if s.Dead {
   139  		return "dead"
   140  	}
   141  
   142  	if s.StartedAt.IsZero() {
   143  		return "created"
   144  	}
   145  
   146  	return "exited"
   147  }
   148  
   149  // IsValidStateString checks if the provided string is a valid container state or not.
   150  func IsValidStateString(s string) bool {
   151  	if s != "paused" &&
   152  		s != "restarting" &&
   153  		s != "removing" &&
   154  		s != "running" &&
   155  		s != "dead" &&
   156  		s != "created" &&
   157  		s != "exited" {
   158  		return false
   159  	}
   160  	return true
   161  }
   162  
   163  // WaitCondition is an enum type for different states to wait for.
   164  type WaitCondition int
   165  
   166  // Possible WaitCondition Values.
   167  //
   168  // WaitConditionNotRunning (default) is used to wait for any of the non-running
   169  // states: "created", "exited", "dead", "removing", or "removed".
   170  //
   171  // WaitConditionNextExit is used to wait for the next time the state changes
   172  // to a non-running state. If the state is currently "created" or "exited",
   173  // this would cause Wait() to block until either the container runs and exits
   174  // or is removed.
   175  //
   176  // WaitConditionRemoved is used to wait for the container to be removed.
   177  const (
   178  	WaitConditionNotRunning WaitCondition = iota
   179  	WaitConditionNextExit
   180  	WaitConditionRemoved
   181  )
   182  
   183  // Wait waits until the container is in a certain state indicated by the given
   184  // condition. A context must be used for cancelling the request, controlling
   185  // timeouts, and avoiding goroutine leaks. Wait must be called without holding
   186  // the state lock. Returns a channel from which the caller will receive the
   187  // result. If the container exited on its own, the result's Err() method will
   188  // be nil and its ExitCode() method will return the container's exit code,
   189  // otherwise, the results Err() method will return an error indicating why the
   190  // wait operation failed.
   191  func (s *State) Wait(ctx context.Context, condition WaitCondition) <-chan StateStatus {
   192  	s.Lock()
   193  	defer s.Unlock()
   194  
   195  	if condition == WaitConditionNotRunning && !s.Running {
   196  		// Buffer so we can put it in the channel now.
   197  		resultC := make(chan StateStatus, 1)
   198  
   199  		// Send the current status.
   200  		resultC <- StateStatus{
   201  			exitCode: s.ExitCode(),
   202  			err:      s.Err(),
   203  		}
   204  
   205  		return resultC
   206  	}
   207  
   208  	// If we are waiting only for removal, the waitStop channel should
   209  	// remain nil and block forever.
   210  	var waitStop chan struct{}
   211  	if condition < WaitConditionRemoved {
   212  		waitStop = s.waitStop
   213  	}
   214  
   215  	// Always wait for removal, just in case the container gets removed
   216  	// while it is still in a "created" state, in which case it is never
   217  	// actually stopped.
   218  	waitRemove := s.waitRemove
   219  
   220  	resultC := make(chan StateStatus)
   221  
   222  	go func() {
   223  		select {
   224  		case <-ctx.Done():
   225  			// Context timeout or cancellation.
   226  			resultC <- StateStatus{
   227  				exitCode: -1,
   228  				err:      ctx.Err(),
   229  			}
   230  			return
   231  		case <-waitStop:
   232  		case <-waitRemove:
   233  		}
   234  
   235  		s.Lock()
   236  		result := StateStatus{
   237  			exitCode: s.ExitCode(),
   238  			err:      s.Err(),
   239  		}
   240  		s.Unlock()
   241  
   242  		resultC <- result
   243  	}()
   244  
   245  	return resultC
   246  }
   247  
   248  // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
   249  func (s *State) IsRunning() bool {
   250  	s.Lock()
   251  	res := s.Running
   252  	s.Unlock()
   253  	return res
   254  }
   255  
   256  // GetPID holds the process id of a container.
   257  func (s *State) GetPID() int {
   258  	s.Lock()
   259  	res := s.Pid
   260  	s.Unlock()
   261  	return res
   262  }
   263  
   264  // ExitCode returns current exitcode for the state. Take lock before if state
   265  // may be shared.
   266  func (s *State) ExitCode() int {
   267  	return s.ExitCodeValue
   268  }
   269  
   270  // SetExitCode sets current exitcode for the state. Take lock before if state
   271  // may be shared.
   272  func (s *State) SetExitCode(ec int) {
   273  	s.ExitCodeValue = ec
   274  }
   275  
   276  // SetRunning sets the state of the container to "running".
   277  func (s *State) SetRunning(pid int, initial bool) {
   278  	s.ErrorMsg = ""
   279  	s.Paused = false
   280  	s.Running = true
   281  	s.Restarting = false
   282  	if initial {
   283  		s.Paused = false
   284  	}
   285  	s.ExitCodeValue = 0
   286  	s.Pid = pid
   287  	if initial {
   288  		s.StartedAt = time.Now().UTC()
   289  	}
   290  }
   291  
   292  // SetStopped sets the container state to "stopped" without locking.
   293  func (s *State) SetStopped(exitStatus *ExitStatus) {
   294  	s.Running = false
   295  	s.Paused = false
   296  	s.Restarting = false
   297  	s.Pid = 0
   298  	if exitStatus.ExitedAt.IsZero() {
   299  		s.FinishedAt = time.Now().UTC()
   300  	} else {
   301  		s.FinishedAt = exitStatus.ExitedAt
   302  	}
   303  	s.ExitCodeValue = exitStatus.ExitCode
   304  	s.OOMKilled = exitStatus.OOMKilled
   305  	close(s.waitStop) // fire waiters for stop
   306  	s.waitStop = make(chan struct{})
   307  }
   308  
   309  // SetRestarting sets the container state to "restarting" without locking.
   310  // It also sets the container PID to 0.
   311  func (s *State) SetRestarting(exitStatus *ExitStatus) {
   312  	// we should consider the container running when it is restarting because of
   313  	// all the checks in docker around rm/stop/etc
   314  	s.Running = true
   315  	s.Restarting = true
   316  	s.Paused = false
   317  	s.Pid = 0
   318  	s.FinishedAt = time.Now().UTC()
   319  	s.ExitCodeValue = exitStatus.ExitCode
   320  	s.OOMKilled = exitStatus.OOMKilled
   321  	close(s.waitStop) // fire waiters for stop
   322  	s.waitStop = make(chan struct{})
   323  }
   324  
   325  // SetError sets the container's error state. This is useful when we want to
   326  // know the error that occurred when container transits to another state
   327  // when inspecting it
   328  func (s *State) SetError(err error) {
   329  	s.ErrorMsg = ""
   330  	if err != nil {
   331  		s.ErrorMsg = err.Error()
   332  	}
   333  }
   334  
   335  // IsPaused returns whether the container is paused or not.
   336  func (s *State) IsPaused() bool {
   337  	s.Lock()
   338  	res := s.Paused
   339  	s.Unlock()
   340  	return res
   341  }
   342  
   343  // IsRestarting returns whether the container is restarting or not.
   344  func (s *State) IsRestarting() bool {
   345  	s.Lock()
   346  	res := s.Restarting
   347  	s.Unlock()
   348  	return res
   349  }
   350  
   351  // SetRemovalInProgress sets the container state as being removed.
   352  // It returns true if the container was already in that state.
   353  func (s *State) SetRemovalInProgress() bool {
   354  	s.Lock()
   355  	defer s.Unlock()
   356  	if s.RemovalInProgress {
   357  		return true
   358  	}
   359  	s.RemovalInProgress = true
   360  	return false
   361  }
   362  
   363  // ResetRemovalInProgress makes the RemovalInProgress state to false.
   364  func (s *State) ResetRemovalInProgress() {
   365  	s.Lock()
   366  	s.RemovalInProgress = false
   367  	s.Unlock()
   368  }
   369  
   370  // IsRemovalInProgress returns whether the RemovalInProgress flag is set.
   371  // Used by Container to check whether a container is being removed.
   372  func (s *State) IsRemovalInProgress() bool {
   373  	s.Lock()
   374  	res := s.RemovalInProgress
   375  	s.Unlock()
   376  	return res
   377  }
   378  
   379  // SetDead sets the container state to "dead"
   380  func (s *State) SetDead() {
   381  	s.Lock()
   382  	s.Dead = true
   383  	s.Unlock()
   384  }
   385  
   386  // IsDead returns whether the Dead flag is set. Used by Container to check whether a container is dead.
   387  func (s *State) IsDead() bool {
   388  	s.Lock()
   389  	res := s.Dead
   390  	s.Unlock()
   391  	return res
   392  }
   393  
   394  // SetRemoved assumes this container is already in the "dead" state and
   395  // closes the internal waitRemove channel to unblock callers waiting for a
   396  // container to be removed.
   397  func (s *State) SetRemoved() {
   398  	s.SetRemovalError(nil)
   399  }
   400  
   401  // SetRemovalError is to be called in case a container remove failed.
   402  // It sets an error and closes the internal waitRemove channel to unblock
   403  // callers waiting for the container to be removed.
   404  func (s *State) SetRemovalError(err error) {
   405  	s.SetError(err)
   406  	s.Lock()
   407  	close(s.waitRemove) // Unblock those waiting on remove.
   408  	// Recreate the channel so next ContainerWait will work
   409  	s.waitRemove = make(chan struct{})
   410  	s.Unlock()
   411  }
   412  
   413  // Err returns an error if there is one.
   414  func (s *State) Err() error {
   415  	if s.ErrorMsg != "" {
   416  		return errors.New(s.ErrorMsg)
   417  	}
   418  	return nil
   419  }