Bladeren bron

Merge pull request #26158 from mlaventure/reexport-exitcode

Reexport exitcode
Alexander Morozov 8 jaren geleden
bovenliggende
commit
0fc393674c
5 gewijzigde bestanden met toevoegingen van 84 en 21 verwijderingen
  1. 43 18
      container/state.go
  2. 1 1
      container/state_solaris.go
  3. 1 1
      container/state_unix.go
  4. 1 1
      container/state_windows.go
  5. 38 0
      integration-cli/docker_cli_daemon_test.go

+ 43 - 18
container/state.go

@@ -24,14 +24,40 @@ type State struct {
 	RemovalInProgress bool // Not need for this to be persistent on disk.
 	RemovalInProgress bool // Not need for this to be persistent on disk.
 	Dead              bool
 	Dead              bool
 	Pid               int
 	Pid               int
-	exitCode          int
-	error             string // contains last known error when starting the container
+	ExitCodeValue     int    `json:"ExitCode"`
+	ErrorMsg          string `json:"Error"` // contains last known error when starting the container
 	StartedAt         time.Time
 	StartedAt         time.Time
 	FinishedAt        time.Time
 	FinishedAt        time.Time
 	waitChan          chan struct{}
 	waitChan          chan struct{}
 	Health            *Health
 	Health            *Health
 }
 }
 
 
+// StateStatus is used to return an error type implementing both
+// exec.ExitCode and error.
+// This type is needed as State include a sync.Mutex field which make
+// copying it unsafe.
+type StateStatus struct {
+	exitCode int
+	error    string
+}
+
+func newStateStatus(ec int, err string) *StateStatus {
+	return &StateStatus{
+		exitCode: ec,
+		error:    err,
+	}
+}
+
+// ExitCode returns current exitcode for the state.
+func (ss *StateStatus) ExitCode() int {
+	return ss.exitCode
+}
+
+// Error returns current error for the state.
+func (ss *StateStatus) Error() string {
+	return ss.error
+}
+
 // NewState creates a default state object with a fresh channel for state changes.
 // NewState creates a default state object with a fresh channel for state changes.
 func NewState() *State {
 func NewState() *State {
 	return &State{
 	return &State{
@@ -46,7 +72,7 @@ func (s *State) String() string {
 			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
 			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
 		}
 		}
 		if s.Restarting {
 		if s.Restarting {
-			return fmt.Sprintf("Restarting (%d) %s ago", s.exitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+			return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
 		}
 		}
 
 
 		if h := s.Health; h != nil {
 		if h := s.Health; h != nil {
@@ -71,7 +97,7 @@ func (s *State) String() string {
 		return ""
 		return ""
 	}
 	}
 
 
-	return fmt.Sprintf("Exited (%d) %s ago", s.exitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
 }
 }
 
 
 // StateString returns a single string to describe state
 // StateString returns a single string to describe state
@@ -134,7 +160,7 @@ func wait(waitChan <-chan struct{}, timeout time.Duration) error {
 func (s *State) WaitStop(timeout time.Duration) (int, error) {
 func (s *State) WaitStop(timeout time.Duration) (int, error) {
 	s.Lock()
 	s.Lock()
 	if !s.Running {
 	if !s.Running {
-		exitCode := s.exitCode
+		exitCode := s.ExitCodeValue
 		s.Unlock()
 		s.Unlock()
 		return exitCode, nil
 		return exitCode, nil
 	}
 	}
@@ -154,24 +180,24 @@ func (s *State) WaitWithContext(ctx context.Context) error {
 	// todo(tonistiigi): make other wait functions use this
 	// todo(tonistiigi): make other wait functions use this
 	s.Lock()
 	s.Lock()
 	if !s.Running {
 	if !s.Running {
-		state := *s
+		state := newStateStatus(s.ExitCode(), s.Error())
 		defer s.Unlock()
 		defer s.Unlock()
-		if state.exitCode == 0 {
+		if state.ExitCode() == 0 {
 			return nil
 			return nil
 		}
 		}
-		return &state
+		return state
 	}
 	}
 	waitChan := s.waitChan
 	waitChan := s.waitChan
 	s.Unlock()
 	s.Unlock()
 	select {
 	select {
 	case <-waitChan:
 	case <-waitChan:
 		s.Lock()
 		s.Lock()
-		state := *s
+		state := newStateStatus(s.ExitCode(), s.Error())
 		s.Unlock()
 		s.Unlock()
-		if state.exitCode == 0 {
+		if state.ExitCode() == 0 {
 			return nil
 			return nil
 		}
 		}
-		return &state
+		return state
 	case <-ctx.Done():
 	case <-ctx.Done():
 		return ctx.Err()
 		return ctx.Err()
 	}
 	}
@@ -196,22 +222,21 @@ func (s *State) GetPID() int {
 // ExitCode returns current exitcode for the state. Take lock before if state
 // ExitCode returns current exitcode for the state. Take lock before if state
 // may be shared.
 // may be shared.
 func (s *State) ExitCode() int {
 func (s *State) ExitCode() int {
-	res := s.exitCode
-	return res
+	return s.ExitCodeValue
 }
 }
 
 
 // SetExitCode sets current exitcode for the state. Take lock before if state
 // SetExitCode sets current exitcode for the state. Take lock before if state
 // may be shared.
 // may be shared.
 func (s *State) SetExitCode(ec int) {
 func (s *State) SetExitCode(ec int) {
-	s.exitCode = ec
+	s.ExitCodeValue = ec
 }
 }
 
 
 // SetRunning sets the state of the container to "running".
 // SetRunning sets the state of the container to "running".
 func (s *State) SetRunning(pid int, initial bool) {
 func (s *State) SetRunning(pid int, initial bool) {
-	s.error = ""
+	s.ErrorMsg = ""
 	s.Running = true
 	s.Running = true
 	s.Restarting = false
 	s.Restarting = false
-	s.exitCode = 0
+	s.ExitCodeValue = 0
 	s.Pid = pid
 	s.Pid = pid
 	if initial {
 	if initial {
 		s.StartedAt = time.Now().UTC()
 		s.StartedAt = time.Now().UTC()
@@ -263,7 +288,7 @@ func (s *State) SetRestarting(exitStatus *ExitStatus) {
 // know the error that occurred when container transits to another state
 // know the error that occurred when container transits to another state
 // when inspecting it
 // when inspecting it
 func (s *State) SetError(err error) {
 func (s *State) SetError(err error) {
-	s.error = err.Error()
+	s.ErrorMsg = err.Error()
 }
 }
 
 
 // IsPaused returns whether the container is paused or not.
 // IsPaused returns whether the container is paused or not.
@@ -310,5 +335,5 @@ func (s *State) SetDead() {
 
 
 // Error returns current error for the state.
 // Error returns current error for the state.
 func (s *State) Error() string {
 func (s *State) Error() string {
-	return s.error
+	return s.ErrorMsg
 }
 }

+ 1 - 1
container/state_solaris.go

@@ -3,5 +3,5 @@ package container
 // setFromExitStatus is a platform specific helper function to set the state
 // setFromExitStatus is a platform specific helper function to set the state
 // based on the ExitStatus structure.
 // based on the ExitStatus structure.
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
-	s.exitCode = exitStatus.ExitCode
+	s.ExitCodeValue = exitStatus.ExitCode
 }
 }

+ 1 - 1
container/state_unix.go

@@ -5,6 +5,6 @@ package container
 // setFromExitStatus is a platform specific helper function to set the state
 // setFromExitStatus is a platform specific helper function to set the state
 // based on the ExitStatus structure.
 // based on the ExitStatus structure.
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
-	s.exitCode = exitStatus.ExitCode
+	s.ExitCodeValue = exitStatus.ExitCode
 	s.OOMKilled = exitStatus.OOMKilled
 	s.OOMKilled = exitStatus.OOMKilled
 }
 }

+ 1 - 1
container/state_windows.go

@@ -3,5 +3,5 @@ package container
 // setFromExitStatus is a platform specific helper function to set the state
 // setFromExitStatus is a platform specific helper function to set the state
 // based on the ExitStatus structure.
 // based on the ExitStatus structure.
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
 func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
-	s.exitCode = exitStatus.ExitCode
+	s.ExitCodeValue = exitStatus.ExitCode
 }
 }

+ 38 - 0
integration-cli/docker_cli_daemon_test.go

@@ -2764,3 +2764,41 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithAutoRemoveContainer(c *check.C)
 	c.Assert(out, checker.Contains, "top1", check.Commentf("top1 should exist after daemon restarts"))
 	c.Assert(out, checker.Contains, "top1", check.Commentf("top1 should exist after daemon restarts"))
 	c.Assert(out, checker.Not(checker.Contains), "top2", check.Commentf("top2 should be removed after daemon restarts"))
 	c.Assert(out, checker.Not(checker.Contains), "top2", check.Commentf("top2 should be removed after daemon restarts"))
 }
 }
+
+func (s *DockerDaemonSuite) TestDaemonRestartSaveContainerExitCode(c *check.C) {
+	err := s.d.StartWithBusybox()
+	c.Assert(err, checker.IsNil)
+
+	containerName := "error-values"
+	runError := "oci runtime error: exec: \"toto\": executable file not found in $PATH"
+	// Make a container with both a non 0 exit code and an error message
+	out, err := s.d.Cmd("run", "--name", containerName, "busybox", "toto")
+	c.Assert(err, checker.NotNil)
+	c.Assert(out, checker.Contains, runError)
+
+	// Check that those values were saved on disk
+	out, err = s.d.Cmd("inspect", "-f", "{{.State.ExitCode}}", containerName)
+	out = strings.TrimSpace(out)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Equals, "127")
+
+	out, err = s.d.Cmd("inspect", "-f", "{{.State.Error}}", containerName)
+	out = strings.TrimSpace(out)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Equals, runError)
+
+	// now restart daemon
+	err = s.d.Restart()
+	c.Assert(err, checker.IsNil)
+
+	// Check that those values are still around
+	out, err = s.d.Cmd("inspect", "-f", "{{.State.ExitCode}}", containerName)
+	out = strings.TrimSpace(out)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Equals, "127")
+
+	out, err = s.d.Cmd("inspect", "-f", "{{.State.Error}}", containerName)
+	out = strings.TrimSpace(out)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Equals, runError)
+}