github.com/nullne/docker@v1.13.0-rc1/libcontainerd/client_linux.go (about)

     1  package libcontainerd
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	containerd "github.com/docker/containerd/api/grpc/types"
    13  	"github.com/docker/docker/pkg/ioutils"
    14  	"github.com/docker/docker/pkg/mount"
    15  	"github.com/golang/protobuf/ptypes"
    16  	"github.com/golang/protobuf/ptypes/timestamp"
    17  	specs "github.com/opencontainers/runtime-spec/specs-go"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  type client struct {
    22  	clientCommon
    23  
    24  	// Platform specific properties below here.
    25  	remote        *remote
    26  	q             queue
    27  	exitNotifiers map[string]*exitNotifier
    28  	liveRestore   bool
    29  }
    30  
    31  // GetServerVersion returns the connected server version information
    32  func (clnt *client) GetServerVersion(ctx context.Context) (*ServerVersion, error) {
    33  	resp, err := clnt.remote.apiClient.GetServerVersion(ctx, &containerd.GetServerVersionRequest{})
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	sv := &ServerVersion{
    39  		GetServerVersionResponse: *resp,
    40  	}
    41  
    42  	return sv, nil
    43  }
    44  
    45  // AddProcess is the handler for adding a process to an already running
    46  // container. It's called through docker exec. It returns the system pid of the
    47  // exec'd process.
    48  func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, specp Process, attachStdio StdioCallback) (int, error) {
    49  	clnt.lock(containerID)
    50  	defer clnt.unlock(containerID)
    51  	container, err := clnt.getContainer(containerID)
    52  	if err != nil {
    53  		return -1, err
    54  	}
    55  
    56  	spec, err := container.spec()
    57  	if err != nil {
    58  		return -1, err
    59  	}
    60  	sp := spec.Process
    61  	sp.Args = specp.Args
    62  	sp.Terminal = specp.Terminal
    63  	if len(specp.Env) > 0 {
    64  		sp.Env = specp.Env
    65  	}
    66  	if specp.Cwd != nil {
    67  		sp.Cwd = *specp.Cwd
    68  	}
    69  	if specp.User != nil {
    70  		sp.User = specs.User{
    71  			UID:            specp.User.UID,
    72  			GID:            specp.User.GID,
    73  			AdditionalGids: specp.User.AdditionalGids,
    74  		}
    75  	}
    76  	if specp.Capabilities != nil {
    77  		sp.Capabilities = specp.Capabilities
    78  	}
    79  
    80  	p := container.newProcess(processFriendlyName)
    81  
    82  	r := &containerd.AddProcessRequest{
    83  		Args:     sp.Args,
    84  		Cwd:      sp.Cwd,
    85  		Terminal: sp.Terminal,
    86  		Id:       containerID,
    87  		Env:      sp.Env,
    88  		User: &containerd.User{
    89  			Uid:            sp.User.UID,
    90  			Gid:            sp.User.GID,
    91  			AdditionalGids: sp.User.AdditionalGids,
    92  		},
    93  		Pid:             processFriendlyName,
    94  		Stdin:           p.fifo(syscall.Stdin),
    95  		Stdout:          p.fifo(syscall.Stdout),
    96  		Stderr:          p.fifo(syscall.Stderr),
    97  		Capabilities:    sp.Capabilities,
    98  		ApparmorProfile: sp.ApparmorProfile,
    99  		SelinuxLabel:    sp.SelinuxLabel,
   100  		NoNewPrivileges: sp.NoNewPrivileges,
   101  		Rlimits:         convertRlimits(sp.Rlimits),
   102  	}
   103  
   104  	iopipe, err := p.openFifos(sp.Terminal)
   105  	if err != nil {
   106  		return -1, err
   107  	}
   108  
   109  	resp, err := clnt.remote.apiClient.AddProcess(ctx, r)
   110  	if err != nil {
   111  		p.closeFifos(iopipe)
   112  		return -1, err
   113  	}
   114  
   115  	var stdinOnce sync.Once
   116  	stdin := iopipe.Stdin
   117  	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
   118  		var err error
   119  		stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
   120  			err = stdin.Close()
   121  			if err2 := p.sendCloseStdin(); err == nil {
   122  				err = err2
   123  			}
   124  		})
   125  		return err
   126  	})
   127  
   128  	container.processes[processFriendlyName] = p
   129  
   130  	if err := attachStdio(*iopipe); err != nil {
   131  		p.closeFifos(iopipe)
   132  		return -1, err
   133  	}
   134  
   135  	return int(resp.SystemPid), nil
   136  }
   137  
   138  func (clnt *client) SignalProcess(containerID string, pid string, sig int) error {
   139  	clnt.lock(containerID)
   140  	defer clnt.unlock(containerID)
   141  	_, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{
   142  		Id:     containerID,
   143  		Pid:    pid,
   144  		Signal: uint32(sig),
   145  	})
   146  	return err
   147  }
   148  
   149  func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
   150  	clnt.lock(containerID)
   151  	defer clnt.unlock(containerID)
   152  	if _, err := clnt.getContainer(containerID); err != nil {
   153  		return err
   154  	}
   155  	_, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{
   156  		Id:     containerID,
   157  		Pid:    processFriendlyName,
   158  		Width:  uint32(width),
   159  		Height: uint32(height),
   160  	})
   161  	return err
   162  }
   163  
   164  func (clnt *client) Pause(containerID string) error {
   165  	return clnt.setState(containerID, StatePause)
   166  }
   167  
   168  func (clnt *client) setState(containerID, state string) error {
   169  	clnt.lock(containerID)
   170  	container, err := clnt.getContainer(containerID)
   171  	if err != nil {
   172  		clnt.unlock(containerID)
   173  		return err
   174  	}
   175  	if container.systemPid == 0 {
   176  		clnt.unlock(containerID)
   177  		return fmt.Errorf("No active process for container %s", containerID)
   178  	}
   179  	st := "running"
   180  	if state == StatePause {
   181  		st = "paused"
   182  	}
   183  	chstate := make(chan struct{})
   184  	_, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{
   185  		Id:     containerID,
   186  		Pid:    InitFriendlyName,
   187  		Status: st,
   188  	})
   189  	if err != nil {
   190  		clnt.unlock(containerID)
   191  		return err
   192  	}
   193  	container.pauseMonitor.append(state, chstate)
   194  	clnt.unlock(containerID)
   195  	<-chstate
   196  	return nil
   197  }
   198  
   199  func (clnt *client) Resume(containerID string) error {
   200  	return clnt.setState(containerID, StateResume)
   201  }
   202  
   203  func (clnt *client) Stats(containerID string) (*Stats, error) {
   204  	resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID})
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	return (*Stats)(resp), nil
   209  }
   210  
   211  // Take care of the old 1.11.0 behavior in case the version upgrade
   212  // happened without a clean daemon shutdown
   213  func (clnt *client) cleanupOldRootfs(containerID string) {
   214  	// Unmount and delete the bundle folder
   215  	if mts, err := mount.GetMounts(); err == nil {
   216  		for _, mts := range mts {
   217  			if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") {
   218  				if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil {
   219  					os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs"))
   220  				}
   221  				break
   222  			}
   223  		}
   224  	}
   225  }
   226  
   227  func (clnt *client) setExited(containerID string, exitCode uint32) error {
   228  	clnt.lock(containerID)
   229  	defer clnt.unlock(containerID)
   230  
   231  	err := clnt.backend.StateChanged(containerID, StateInfo{
   232  		CommonStateInfo: CommonStateInfo{
   233  			State:    StateExit,
   234  			ExitCode: exitCode,
   235  		}})
   236  
   237  	clnt.cleanupOldRootfs(containerID)
   238  
   239  	return err
   240  }
   241  
   242  func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
   243  	cont, err := clnt.getContainerdContainer(containerID)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	pids := make([]int, len(cont.Pids))
   248  	for i, p := range cont.Pids {
   249  		pids[i] = int(p)
   250  	}
   251  	return pids, nil
   252  }
   253  
   254  // Summary returns a summary of the processes running in a container.
   255  // This is a no-op on Linux.
   256  func (clnt *client) Summary(containerID string) ([]Summary, error) {
   257  	return nil, nil
   258  }
   259  
   260  func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) {
   261  	resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID})
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	for _, cont := range resp.Containers {
   266  		if cont.Id == containerID {
   267  			return cont, nil
   268  		}
   269  	}
   270  	return nil, fmt.Errorf("invalid state response")
   271  }
   272  
   273  func (clnt *client) UpdateResources(containerID string, resources Resources) error {
   274  	clnt.lock(containerID)
   275  	defer clnt.unlock(containerID)
   276  	container, err := clnt.getContainer(containerID)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	if container.systemPid == 0 {
   281  		return fmt.Errorf("No active process for container %s", containerID)
   282  	}
   283  	_, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{
   284  		Id:        containerID,
   285  		Pid:       InitFriendlyName,
   286  		Resources: (*containerd.UpdateResource)(&resources),
   287  	})
   288  	if err != nil {
   289  		return err
   290  	}
   291  	return nil
   292  }
   293  
   294  func (clnt *client) getExitNotifier(containerID string) *exitNotifier {
   295  	clnt.mapMutex.RLock()
   296  	defer clnt.mapMutex.RUnlock()
   297  	return clnt.exitNotifiers[containerID]
   298  }
   299  
   300  func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier {
   301  	clnt.mapMutex.Lock()
   302  	w, ok := clnt.exitNotifiers[containerID]
   303  	defer clnt.mapMutex.Unlock()
   304  	if !ok {
   305  		w = &exitNotifier{c: make(chan struct{}), client: clnt}
   306  		clnt.exitNotifiers[containerID] = w
   307  	}
   308  	return w
   309  }
   310  
   311  func (clnt *client) restore(cont *containerd.Container, lastEvent *containerd.Event, attachStdio StdioCallback, options ...CreateOption) (err error) {
   312  	clnt.lock(cont.Id)
   313  	defer clnt.unlock(cont.Id)
   314  
   315  	logrus.Debugf("libcontainerd: restore container %s state %s", cont.Id, cont.Status)
   316  
   317  	containerID := cont.Id
   318  	if _, err := clnt.getContainer(containerID); err == nil {
   319  		return fmt.Errorf("container %s is already active", containerID)
   320  	}
   321  
   322  	defer func() {
   323  		if err != nil {
   324  			clnt.deleteContainer(cont.Id)
   325  		}
   326  	}()
   327  
   328  	container := clnt.newContainer(cont.BundlePath, options...)
   329  	container.systemPid = systemPid(cont)
   330  
   331  	var terminal bool
   332  	for _, p := range cont.Processes {
   333  		if p.Pid == InitFriendlyName {
   334  			terminal = p.Terminal
   335  		}
   336  	}
   337  
   338  	iopipe, err := container.openFifos(terminal)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	var stdinOnce sync.Once
   343  	stdin := iopipe.Stdin
   344  	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
   345  		var err error
   346  		stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
   347  			err = stdin.Close()
   348  		})
   349  		return err
   350  	})
   351  
   352  	if err := attachStdio(*iopipe); err != nil {
   353  		container.closeFifos(iopipe)
   354  		return err
   355  	}
   356  
   357  	clnt.appendContainer(container)
   358  
   359  	err = clnt.backend.StateChanged(containerID, StateInfo{
   360  		CommonStateInfo: CommonStateInfo{
   361  			State: StateRestore,
   362  			Pid:   container.systemPid,
   363  		}})
   364  
   365  	if err != nil {
   366  		container.closeFifos(iopipe)
   367  		return err
   368  	}
   369  
   370  	if lastEvent != nil {
   371  		// This should only be a pause or resume event
   372  		if lastEvent.Type == StatePause || lastEvent.Type == StateResume {
   373  			return clnt.backend.StateChanged(containerID, StateInfo{
   374  				CommonStateInfo: CommonStateInfo{
   375  					State: lastEvent.Type,
   376  					Pid:   container.systemPid,
   377  				}})
   378  		}
   379  
   380  		logrus.Warnf("libcontainerd: unexpected backlog event: %#v", lastEvent)
   381  	}
   382  
   383  	return nil
   384  }
   385  
   386  func (clnt *client) getContainerLastEventSinceTime(id string, tsp *timestamp.Timestamp) (*containerd.Event, error) {
   387  	er := &containerd.EventsRequest{
   388  		Timestamp:  tsp,
   389  		StoredOnly: true,
   390  		Id:         id,
   391  	}
   392  	events, err := clnt.remote.apiClient.Events(context.Background(), er)
   393  	if err != nil {
   394  		logrus.Errorf("libcontainerd: failed to get container events stream for %s: %q", er.Id, err)
   395  		return nil, err
   396  	}
   397  
   398  	var ev *containerd.Event
   399  	for {
   400  		e, err := events.Recv()
   401  		if err != nil {
   402  			if err.Error() == "EOF" {
   403  				break
   404  			}
   405  			logrus.Errorf("libcontainerd: failed to get container event for %s: %q", id, err)
   406  			return nil, err
   407  		}
   408  
   409  		logrus.Debugf("libcontainerd: received past event %#v", e)
   410  
   411  		switch e.Type {
   412  		case StateExit, StatePause, StateResume:
   413  			ev = e
   414  		}
   415  	}
   416  
   417  	return ev, nil
   418  }
   419  
   420  func (clnt *client) getContainerLastEvent(id string) (*containerd.Event, error) {
   421  	ev, err := clnt.getContainerLastEventSinceTime(id, clnt.remote.restoreFromTimestamp)
   422  	if err == nil && ev == nil {
   423  		// If ev is nil and the container is running in containerd,
   424  		// we already consumed all the event of the
   425  		// container, included the "exit" one.
   426  		// Thus, we request all events containerd has in memory for
   427  		// this container in order to get the last one (which should
   428  		// be an exit event)
   429  		logrus.Warnf("libcontainerd: client is out of sync, restore was called on a fully synced container (%s).", id)
   430  		// Request all events since beginning of time
   431  		t := time.Unix(0, 0)
   432  		tsp, err := ptypes.TimestampProto(t)
   433  		if err != nil {
   434  			logrus.Errorf("libcontainerd: getLastEventSinceTime() failed to convert timestamp: %q", err)
   435  			return nil, err
   436  		}
   437  
   438  		return clnt.getContainerLastEventSinceTime(id, tsp)
   439  	}
   440  
   441  	return ev, err
   442  }
   443  
   444  func (clnt *client) Restore(containerID string, attachStdio StdioCallback, options ...CreateOption) error {
   445  	// Synchronize with live events
   446  	clnt.remote.Lock()
   447  	defer clnt.remote.Unlock()
   448  	// Check that containerd still knows this container.
   449  	//
   450  	// In the unlikely event that Restore for this container process
   451  	// the its past event before the main loop, the event will be
   452  	// processed twice. However, this is not an issue as all those
   453  	// events will do is change the state of the container to be
   454  	// exactly the same.
   455  	cont, err := clnt.getContainerdContainer(containerID)
   456  	// Get its last event
   457  	ev, eerr := clnt.getContainerLastEvent(containerID)
   458  	if err != nil || cont.Status == "Stopped" {
   459  		if err != nil && !strings.Contains(err.Error(), "container not found") {
   460  			// Legitimate error
   461  			return err
   462  		}
   463  
   464  		if ev == nil {
   465  			if _, err := clnt.getContainer(containerID); err == nil {
   466  				// If ev is nil and the container is running in containerd,
   467  				// we already consumed all the event of the
   468  				// container, included the "exit" one.
   469  				// Thus we return to avoid overriding the Exit Code.
   470  				logrus.Warnf("libcontainerd: restore was called on a fully synced container (%s)", containerID)
   471  				return nil
   472  			}
   473  			// the container is not running so we need to fix the state within docker
   474  			ev = &containerd.Event{
   475  				Type:   StateExit,
   476  				Status: 1,
   477  			}
   478  		}
   479  
   480  		// get the exit status for this container
   481  		ec := uint32(0)
   482  		if eerr == nil && ev.Type == StateExit {
   483  			ec = ev.Status
   484  		}
   485  		clnt.setExited(containerID, ec)
   486  
   487  		return nil
   488  	}
   489  
   490  	// container is still alive
   491  	if clnt.liveRestore {
   492  		if err := clnt.restore(cont, ev, attachStdio, options...); err != nil {
   493  			logrus.Errorf("libcontainerd: error restoring %s: %v", containerID, err)
   494  		}
   495  		return nil
   496  	}
   497  
   498  	// Kill the container if liveRestore == false
   499  	w := clnt.getOrCreateExitNotifier(containerID)
   500  	clnt.lock(cont.Id)
   501  	container := clnt.newContainer(cont.BundlePath)
   502  	container.systemPid = systemPid(cont)
   503  	clnt.appendContainer(container)
   504  	clnt.unlock(cont.Id)
   505  
   506  	container.discardFifos()
   507  
   508  	if err := clnt.Signal(containerID, int(syscall.SIGTERM)); err != nil {
   509  		logrus.Errorf("libcontainerd: error sending sigterm to %v: %v", containerID, err)
   510  	}
   511  	// Let the main loop handle the exit event
   512  	clnt.remote.Unlock()
   513  	select {
   514  	case <-time.After(10 * time.Second):
   515  		if err := clnt.Signal(containerID, int(syscall.SIGKILL)); err != nil {
   516  			logrus.Errorf("libcontainerd: error sending sigkill to %v: %v", containerID, err)
   517  		}
   518  		select {
   519  		case <-time.After(2 * time.Second):
   520  		case <-w.wait():
   521  			// relock because of the defer
   522  			clnt.remote.Lock()
   523  			return nil
   524  		}
   525  	case <-w.wait():
   526  		// relock because of the defer
   527  		clnt.remote.Lock()
   528  		return nil
   529  	}
   530  	// relock because of the defer
   531  	clnt.remote.Lock()
   532  
   533  	clnt.deleteContainer(containerID)
   534  
   535  	return clnt.setExited(containerID, uint32(255))
   536  }
   537  
   538  func (clnt *client) CreateCheckpoint(containerID string, checkpointID string, checkpointDir string, exit bool) error {
   539  	clnt.lock(containerID)
   540  	defer clnt.unlock(containerID)
   541  	if _, err := clnt.getContainer(containerID); err != nil {
   542  		return err
   543  	}
   544  
   545  	_, err := clnt.remote.apiClient.CreateCheckpoint(context.Background(), &containerd.CreateCheckpointRequest{
   546  		Id: containerID,
   547  		Checkpoint: &containerd.Checkpoint{
   548  			Name:        checkpointID,
   549  			Exit:        exit,
   550  			Tcp:         true,
   551  			UnixSockets: true,
   552  			Shell:       false,
   553  			EmptyNS:     []string{"network"},
   554  		},
   555  		CheckpointDir: checkpointDir,
   556  	})
   557  	return err
   558  }
   559  
   560  func (clnt *client) DeleteCheckpoint(containerID string, checkpointID string, checkpointDir string) error {
   561  	clnt.lock(containerID)
   562  	defer clnt.unlock(containerID)
   563  	if _, err := clnt.getContainer(containerID); err != nil {
   564  		return err
   565  	}
   566  
   567  	_, err := clnt.remote.apiClient.DeleteCheckpoint(context.Background(), &containerd.DeleteCheckpointRequest{
   568  		Id:            containerID,
   569  		Name:          checkpointID,
   570  		CheckpointDir: checkpointDir,
   571  	})
   572  	return err
   573  }
   574  
   575  func (clnt *client) ListCheckpoints(containerID string, checkpointDir string) (*Checkpoints, error) {
   576  	clnt.lock(containerID)
   577  	defer clnt.unlock(containerID)
   578  	if _, err := clnt.getContainer(containerID); err != nil {
   579  		return nil, err
   580  	}
   581  
   582  	resp, err := clnt.remote.apiClient.ListCheckpoint(context.Background(), &containerd.ListCheckpointRequest{
   583  		Id:            containerID,
   584  		CheckpointDir: checkpointDir,
   585  	})
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  	return (*Checkpoints)(resp), nil
   590  }