github.com/lalkh/containerd@v1.4.3/pkg/process/init_state.go (about)

     1  // +build !windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package process
    20  
    21  import (
    22  	"context"
    23  
    24  	runc "github.com/containerd/go-runc"
    25  	google_protobuf "github.com/gogo/protobuf/types"
    26  	"github.com/pkg/errors"
    27  	"github.com/sirupsen/logrus"
    28  )
    29  
    30  type initState interface {
    31  	Start(context.Context) error
    32  	Delete(context.Context) error
    33  	Pause(context.Context) error
    34  	Resume(context.Context) error
    35  	Update(context.Context, *google_protobuf.Any) error
    36  	Checkpoint(context.Context, *CheckpointConfig) error
    37  	Exec(context.Context, string, *ExecConfig) (Process, error)
    38  	Kill(context.Context, uint32, bool) error
    39  	SetExited(int)
    40  	Status(context.Context) (string, error)
    41  }
    42  
    43  type createdState struct {
    44  	p *Init
    45  }
    46  
    47  func (s *createdState) transition(name string) error {
    48  	switch name {
    49  	case "running":
    50  		s.p.initState = &runningState{p: s.p}
    51  	case "stopped":
    52  		s.p.initState = &stoppedState{p: s.p}
    53  	case "deleted":
    54  		s.p.initState = &deletedState{}
    55  	default:
    56  		return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
    57  	}
    58  	return nil
    59  }
    60  
    61  func (s *createdState) Pause(ctx context.Context) error {
    62  	return errors.Errorf("cannot pause task in created state")
    63  }
    64  
    65  func (s *createdState) Resume(ctx context.Context) error {
    66  	return errors.Errorf("cannot resume task in created state")
    67  }
    68  
    69  func (s *createdState) Update(ctx context.Context, r *google_protobuf.Any) error {
    70  	return s.p.update(ctx, r)
    71  }
    72  
    73  func (s *createdState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
    74  	return errors.Errorf("cannot checkpoint a task in created state")
    75  }
    76  
    77  func (s *createdState) Start(ctx context.Context) error {
    78  	if err := s.p.start(ctx); err != nil {
    79  		return err
    80  	}
    81  	return s.transition("running")
    82  }
    83  
    84  func (s *createdState) Delete(ctx context.Context) error {
    85  	if err := s.p.delete(ctx); err != nil {
    86  		return err
    87  	}
    88  	return s.transition("deleted")
    89  }
    90  
    91  func (s *createdState) Kill(ctx context.Context, sig uint32, all bool) error {
    92  	return s.p.kill(ctx, sig, all)
    93  }
    94  
    95  func (s *createdState) SetExited(status int) {
    96  	s.p.setExited(status)
    97  
    98  	if err := s.transition("stopped"); err != nil {
    99  		panic(err)
   100  	}
   101  }
   102  
   103  func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   104  	return s.p.exec(ctx, path, r)
   105  }
   106  
   107  func (s *createdState) Status(ctx context.Context) (string, error) {
   108  	return "created", nil
   109  }
   110  
   111  type createdCheckpointState struct {
   112  	p    *Init
   113  	opts *runc.RestoreOpts
   114  }
   115  
   116  func (s *createdCheckpointState) transition(name string) error {
   117  	switch name {
   118  	case "running":
   119  		s.p.initState = &runningState{p: s.p}
   120  	case "stopped":
   121  		s.p.initState = &stoppedState{p: s.p}
   122  	case "deleted":
   123  		s.p.initState = &deletedState{}
   124  	default:
   125  		return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
   126  	}
   127  	return nil
   128  }
   129  
   130  func (s *createdCheckpointState) Pause(ctx context.Context) error {
   131  	return errors.Errorf("cannot pause task in created state")
   132  }
   133  
   134  func (s *createdCheckpointState) Resume(ctx context.Context) error {
   135  	return errors.Errorf("cannot resume task in created state")
   136  }
   137  
   138  func (s *createdCheckpointState) Update(ctx context.Context, r *google_protobuf.Any) error {
   139  	return s.p.update(ctx, r)
   140  }
   141  
   142  func (s *createdCheckpointState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
   143  	return errors.Errorf("cannot checkpoint a task in created state")
   144  }
   145  
   146  func (s *createdCheckpointState) Start(ctx context.Context) error {
   147  	p := s.p
   148  	sio := p.stdio
   149  
   150  	var (
   151  		err    error
   152  		socket *runc.Socket
   153  	)
   154  	if sio.Terminal {
   155  		if socket, err = runc.NewTempConsoleSocket(); err != nil {
   156  			return errors.Wrap(err, "failed to create OCI runtime console socket")
   157  		}
   158  		defer socket.Close()
   159  		s.opts.ConsoleSocket = socket
   160  	}
   161  
   162  	if _, err := s.p.runtime.Restore(ctx, p.id, p.Bundle, s.opts); err != nil {
   163  		return p.runtimeError(err, "OCI runtime restore failed")
   164  	}
   165  	if sio.Stdin != "" {
   166  		if err := p.openStdin(sio.Stdin); err != nil {
   167  			return errors.Wrapf(err, "failed to open stdin fifo %s", sio.Stdin)
   168  		}
   169  	}
   170  	if socket != nil {
   171  		console, err := socket.ReceiveMaster()
   172  		if err != nil {
   173  			return errors.Wrap(err, "failed to retrieve console master")
   174  		}
   175  		console, err = p.Platform.CopyConsole(ctx, console, sio.Stdin, sio.Stdout, sio.Stderr, &p.wg)
   176  		if err != nil {
   177  			return errors.Wrap(err, "failed to start console copy")
   178  		}
   179  		p.console = console
   180  	} else {
   181  		if err := p.io.Copy(ctx, &p.wg); err != nil {
   182  			return errors.Wrap(err, "failed to start io pipe copy")
   183  		}
   184  	}
   185  	pid, err := runc.ReadPidFile(s.opts.PidFile)
   186  	if err != nil {
   187  		return errors.Wrap(err, "failed to retrieve OCI runtime container pid")
   188  	}
   189  	p.pid = pid
   190  	return s.transition("running")
   191  }
   192  
   193  func (s *createdCheckpointState) Delete(ctx context.Context) error {
   194  	if err := s.p.delete(ctx); err != nil {
   195  		return err
   196  	}
   197  	return s.transition("deleted")
   198  }
   199  
   200  func (s *createdCheckpointState) Kill(ctx context.Context, sig uint32, all bool) error {
   201  	return s.p.kill(ctx, sig, all)
   202  }
   203  
   204  func (s *createdCheckpointState) SetExited(status int) {
   205  	s.p.setExited(status)
   206  
   207  	if err := s.transition("stopped"); err != nil {
   208  		panic(err)
   209  	}
   210  }
   211  
   212  func (s *createdCheckpointState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   213  	return nil, errors.Errorf("cannot exec in a created state")
   214  }
   215  
   216  func (s *createdCheckpointState) Status(ctx context.Context) (string, error) {
   217  	return "created", nil
   218  }
   219  
   220  type runningState struct {
   221  	p *Init
   222  }
   223  
   224  func (s *runningState) transition(name string) error {
   225  	switch name {
   226  	case "stopped":
   227  		s.p.initState = &stoppedState{p: s.p}
   228  	case "paused":
   229  		s.p.initState = &pausedState{p: s.p}
   230  	default:
   231  		return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
   232  	}
   233  	return nil
   234  }
   235  
   236  func (s *runningState) Pause(ctx context.Context) error {
   237  	s.p.pausing.set(true)
   238  	// NOTE "pausing" will be returned in the short window
   239  	// after `transition("paused")`, before `pausing` is reset
   240  	// to false. That doesn't break the state machine, just
   241  	// delays the "paused" state a little bit.
   242  	defer s.p.pausing.set(false)
   243  
   244  	if err := s.p.runtime.Pause(ctx, s.p.id); err != nil {
   245  		return s.p.runtimeError(err, "OCI runtime pause failed")
   246  	}
   247  
   248  	return s.transition("paused")
   249  }
   250  
   251  func (s *runningState) Resume(ctx context.Context) error {
   252  	return errors.Errorf("cannot resume a running process")
   253  }
   254  
   255  func (s *runningState) Update(ctx context.Context, r *google_protobuf.Any) error {
   256  	return s.p.update(ctx, r)
   257  }
   258  
   259  func (s *runningState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
   260  	return s.p.checkpoint(ctx, r)
   261  }
   262  
   263  func (s *runningState) Start(ctx context.Context) error {
   264  	return errors.Errorf("cannot start a running process")
   265  }
   266  
   267  func (s *runningState) Delete(ctx context.Context) error {
   268  	return errors.Errorf("cannot delete a running process")
   269  }
   270  
   271  func (s *runningState) Kill(ctx context.Context, sig uint32, all bool) error {
   272  	return s.p.kill(ctx, sig, all)
   273  }
   274  
   275  func (s *runningState) SetExited(status int) {
   276  	s.p.setExited(status)
   277  
   278  	if err := s.transition("stopped"); err != nil {
   279  		panic(err)
   280  	}
   281  }
   282  
   283  func (s *runningState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   284  	return s.p.exec(ctx, path, r)
   285  }
   286  
   287  func (s *runningState) Status(ctx context.Context) (string, error) {
   288  	return "running", nil
   289  }
   290  
   291  type pausedState struct {
   292  	p *Init
   293  }
   294  
   295  func (s *pausedState) transition(name string) error {
   296  	switch name {
   297  	case "running":
   298  		s.p.initState = &runningState{p: s.p}
   299  	case "stopped":
   300  		s.p.initState = &stoppedState{p: s.p}
   301  	default:
   302  		return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
   303  	}
   304  	return nil
   305  }
   306  
   307  func (s *pausedState) Pause(ctx context.Context) error {
   308  	return errors.Errorf("cannot pause a paused container")
   309  }
   310  
   311  func (s *pausedState) Resume(ctx context.Context) error {
   312  	if err := s.p.runtime.Resume(ctx, s.p.id); err != nil {
   313  		return s.p.runtimeError(err, "OCI runtime resume failed")
   314  	}
   315  
   316  	return s.transition("running")
   317  }
   318  
   319  func (s *pausedState) Update(ctx context.Context, r *google_protobuf.Any) error {
   320  	return s.p.update(ctx, r)
   321  }
   322  
   323  func (s *pausedState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
   324  	return s.p.checkpoint(ctx, r)
   325  }
   326  
   327  func (s *pausedState) Start(ctx context.Context) error {
   328  	return errors.Errorf("cannot start a paused process")
   329  }
   330  
   331  func (s *pausedState) Delete(ctx context.Context) error {
   332  	return errors.Errorf("cannot delete a paused process")
   333  }
   334  
   335  func (s *pausedState) Kill(ctx context.Context, sig uint32, all bool) error {
   336  	return s.p.kill(ctx, sig, all)
   337  }
   338  
   339  func (s *pausedState) SetExited(status int) {
   340  	s.p.setExited(status)
   341  
   342  	if err := s.p.runtime.Resume(context.Background(), s.p.id); err != nil {
   343  		logrus.WithError(err).Error("resuming exited container from paused state")
   344  	}
   345  
   346  	if err := s.transition("stopped"); err != nil {
   347  		panic(err)
   348  	}
   349  }
   350  
   351  func (s *pausedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   352  	return nil, errors.Errorf("cannot exec in a paused state")
   353  }
   354  
   355  func (s *pausedState) Status(ctx context.Context) (string, error) {
   356  	return "paused", nil
   357  }
   358  
   359  type stoppedState struct {
   360  	p *Init
   361  }
   362  
   363  func (s *stoppedState) transition(name string) error {
   364  	switch name {
   365  	case "deleted":
   366  		s.p.initState = &deletedState{}
   367  	default:
   368  		return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
   369  	}
   370  	return nil
   371  }
   372  
   373  func (s *stoppedState) Pause(ctx context.Context) error {
   374  	return errors.Errorf("cannot pause a stopped container")
   375  }
   376  
   377  func (s *stoppedState) Resume(ctx context.Context) error {
   378  	return errors.Errorf("cannot resume a stopped container")
   379  }
   380  
   381  func (s *stoppedState) Update(ctx context.Context, r *google_protobuf.Any) error {
   382  	return errors.Errorf("cannot update a stopped container")
   383  }
   384  
   385  func (s *stoppedState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
   386  	return errors.Errorf("cannot checkpoint a stopped container")
   387  }
   388  
   389  func (s *stoppedState) Start(ctx context.Context) error {
   390  	return errors.Errorf("cannot start a stopped process")
   391  }
   392  
   393  func (s *stoppedState) Delete(ctx context.Context) error {
   394  	if err := s.p.delete(ctx); err != nil {
   395  		return err
   396  	}
   397  	return s.transition("deleted")
   398  }
   399  
   400  func (s *stoppedState) Kill(ctx context.Context, sig uint32, all bool) error {
   401  	return s.p.kill(ctx, sig, all)
   402  }
   403  
   404  func (s *stoppedState) SetExited(status int) {
   405  	// no op
   406  }
   407  
   408  func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   409  	return nil, errors.Errorf("cannot exec in a stopped state")
   410  }
   411  
   412  func (s *stoppedState) Status(ctx context.Context) (string, error) {
   413  	return "stopped", nil
   414  }