github.com/go/docker@v1.12.0-rc2/libcontainerd/client_linux.go (about)

     1  package libcontainerd
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	containerd "github.com/docker/containerd/api/grpc/types"
    15  	"github.com/docker/docker/pkg/idtools"
    16  	"github.com/docker/docker/pkg/mount"
    17  	specs "github.com/opencontainers/specs/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  func (clnt *client) AddProcess(containerID, processFriendlyName string, specp Process) error {
    32  	clnt.lock(containerID)
    33  	defer clnt.unlock(containerID)
    34  	container, err := clnt.getContainer(containerID)
    35  	if err != nil {
    36  		return err
    37  	}
    38  
    39  	spec, err := container.spec()
    40  	if err != nil {
    41  		return err
    42  	}
    43  	sp := spec.Process
    44  	sp.Args = specp.Args
    45  	sp.Terminal = specp.Terminal
    46  	if specp.Env != nil {
    47  		sp.Env = specp.Env
    48  	}
    49  	if specp.Cwd != nil {
    50  		sp.Cwd = *specp.Cwd
    51  	}
    52  	if specp.User != nil {
    53  		sp.User = specs.User{
    54  			UID:            specp.User.UID,
    55  			GID:            specp.User.GID,
    56  			AdditionalGids: specp.User.AdditionalGids,
    57  		}
    58  	}
    59  	if specp.Capabilities != nil {
    60  		sp.Capabilities = specp.Capabilities
    61  	}
    62  
    63  	p := container.newProcess(processFriendlyName)
    64  
    65  	r := &containerd.AddProcessRequest{
    66  		Args:     sp.Args,
    67  		Cwd:      sp.Cwd,
    68  		Terminal: sp.Terminal,
    69  		Id:       containerID,
    70  		Env:      sp.Env,
    71  		User: &containerd.User{
    72  			Uid:            sp.User.UID,
    73  			Gid:            sp.User.GID,
    74  			AdditionalGids: sp.User.AdditionalGids,
    75  		},
    76  		Pid:             processFriendlyName,
    77  		Stdin:           p.fifo(syscall.Stdin),
    78  		Stdout:          p.fifo(syscall.Stdout),
    79  		Stderr:          p.fifo(syscall.Stderr),
    80  		Capabilities:    sp.Capabilities,
    81  		ApparmorProfile: sp.ApparmorProfile,
    82  		SelinuxLabel:    sp.SelinuxLabel,
    83  		NoNewPrivileges: sp.NoNewPrivileges,
    84  		Rlimits:         convertRlimits(sp.Rlimits),
    85  	}
    86  
    87  	iopipe, err := p.openFifos(sp.Terminal)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	if _, err := clnt.remote.apiClient.AddProcess(context.Background(), r); err != nil {
    93  		p.closeFifos(iopipe)
    94  		return err
    95  	}
    96  
    97  	container.processes[processFriendlyName] = p
    98  
    99  	clnt.unlock(containerID)
   100  
   101  	if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
   102  		return err
   103  	}
   104  	clnt.lock(containerID)
   105  
   106  	return nil
   107  }
   108  
   109  func (clnt *client) prepareBundleDir(uid, gid int) (string, error) {
   110  	root, err := filepath.Abs(clnt.remote.stateDir)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	if uid == 0 && gid == 0 {
   115  		return root, nil
   116  	}
   117  	p := string(filepath.Separator)
   118  	for _, d := range strings.Split(root, string(filepath.Separator))[1:] {
   119  		p = filepath.Join(p, d)
   120  		fi, err := os.Stat(p)
   121  		if err != nil && !os.IsNotExist(err) {
   122  			return "", err
   123  		}
   124  		if os.IsNotExist(err) || fi.Mode()&1 == 0 {
   125  			p = fmt.Sprintf("%s.%d.%d", p, uid, gid)
   126  			if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) {
   127  				return "", err
   128  			}
   129  		}
   130  	}
   131  	return p, nil
   132  }
   133  
   134  func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) (err error) {
   135  	clnt.lock(containerID)
   136  	defer clnt.unlock(containerID)
   137  
   138  	if ctr, err := clnt.getContainer(containerID); err == nil {
   139  		if ctr.restarting {
   140  			ctr.restartManager.Cancel()
   141  			ctr.clean()
   142  		} else {
   143  			return fmt.Errorf("Container %s is already active", containerID)
   144  		}
   145  	}
   146  
   147  	uid, gid, err := getRootIDs(specs.Spec(spec))
   148  	if err != nil {
   149  		return err
   150  	}
   151  	dir, err := clnt.prepareBundleDir(uid, gid)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	container := clnt.newContainer(filepath.Join(dir, containerID), options...)
   157  	if err := container.clean(); err != nil {
   158  		return err
   159  	}
   160  
   161  	defer func() {
   162  		if err != nil {
   163  			container.clean()
   164  			clnt.deleteContainer(containerID)
   165  		}
   166  	}()
   167  
   168  	if err := idtools.MkdirAllAs(container.dir, 0700, uid, gid); err != nil && !os.IsExist(err) {
   169  		return err
   170  	}
   171  
   172  	f, err := os.Create(filepath.Join(container.dir, configFilename))
   173  	if err != nil {
   174  		return err
   175  	}
   176  	defer f.Close()
   177  	if err := json.NewEncoder(f).Encode(spec); err != nil {
   178  		return err
   179  	}
   180  
   181  	return container.start()
   182  }
   183  
   184  func (clnt *client) Signal(containerID string, sig int) error {
   185  	clnt.lock(containerID)
   186  	defer clnt.unlock(containerID)
   187  	_, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{
   188  		Id:     containerID,
   189  		Pid:    InitFriendlyName,
   190  		Signal: uint32(sig),
   191  	})
   192  	return err
   193  }
   194  
   195  func (clnt *client) SignalProcess(containerID string, pid string, sig int) error {
   196  	clnt.lock(containerID)
   197  	defer clnt.unlock(containerID)
   198  	_, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{
   199  		Id:     containerID,
   200  		Pid:    pid,
   201  		Signal: uint32(sig),
   202  	})
   203  	return err
   204  }
   205  
   206  func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
   207  	clnt.lock(containerID)
   208  	defer clnt.unlock(containerID)
   209  	if _, err := clnt.getContainer(containerID); err != nil {
   210  		return err
   211  	}
   212  	_, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{
   213  		Id:     containerID,
   214  		Pid:    processFriendlyName,
   215  		Width:  uint32(width),
   216  		Height: uint32(height),
   217  	})
   218  	return err
   219  }
   220  
   221  func (clnt *client) Pause(containerID string) error {
   222  	return clnt.setState(containerID, StatePause)
   223  }
   224  
   225  func (clnt *client) setState(containerID, state string) error {
   226  	clnt.lock(containerID)
   227  	container, err := clnt.getContainer(containerID)
   228  	if err != nil {
   229  		clnt.unlock(containerID)
   230  		return err
   231  	}
   232  	if container.systemPid == 0 {
   233  		clnt.unlock(containerID)
   234  		return fmt.Errorf("No active process for container %s", containerID)
   235  	}
   236  	st := "running"
   237  	if state == StatePause {
   238  		st = "paused"
   239  	}
   240  	chstate := make(chan struct{})
   241  	_, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{
   242  		Id:     containerID,
   243  		Pid:    InitFriendlyName,
   244  		Status: st,
   245  	})
   246  	if err != nil {
   247  		clnt.unlock(containerID)
   248  		return err
   249  	}
   250  	container.pauseMonitor.append(state, chstate)
   251  	clnt.unlock(containerID)
   252  	<-chstate
   253  	return nil
   254  }
   255  
   256  func (clnt *client) Resume(containerID string) error {
   257  	return clnt.setState(containerID, StateResume)
   258  }
   259  
   260  func (clnt *client) Stats(containerID string) (*Stats, error) {
   261  	resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID})
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return (*Stats)(resp), nil
   266  }
   267  
   268  // Take care of the old 1.11.0 behavior in case the version upgrade
   269  // happened without a clean daemon shutdown
   270  func (clnt *client) cleanupOldRootfs(containerID string) {
   271  	// Unmount and delete the bundle folder
   272  	if mts, err := mount.GetMounts(); err == nil {
   273  		for _, mts := range mts {
   274  			if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") {
   275  				if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil {
   276  					os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs"))
   277  				}
   278  				break
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  func (clnt *client) setExited(containerID string) error {
   285  	clnt.lock(containerID)
   286  	defer clnt.unlock(containerID)
   287  
   288  	var exitCode uint32
   289  	if event, ok := clnt.remote.pastEvents[containerID]; ok {
   290  		exitCode = event.Status
   291  		delete(clnt.remote.pastEvents, containerID)
   292  	}
   293  
   294  	err := clnt.backend.StateChanged(containerID, StateInfo{
   295  		CommonStateInfo: CommonStateInfo{
   296  			State:    StateExit,
   297  			ExitCode: exitCode,
   298  		}})
   299  
   300  	clnt.cleanupOldRootfs(containerID)
   301  
   302  	return err
   303  }
   304  
   305  func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
   306  	cont, err := clnt.getContainerdContainer(containerID)
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	pids := make([]int, len(cont.Pids))
   311  	for i, p := range cont.Pids {
   312  		pids[i] = int(p)
   313  	}
   314  	return pids, nil
   315  }
   316  
   317  // Summary returns a summary of the processes running in a container.
   318  // This is a no-op on Linux.
   319  func (clnt *client) Summary(containerID string) ([]Summary, error) {
   320  	return nil, nil
   321  }
   322  
   323  func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) {
   324  	resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID})
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	for _, cont := range resp.Containers {
   329  		if cont.Id == containerID {
   330  			return cont, nil
   331  		}
   332  	}
   333  	return nil, fmt.Errorf("invalid state response")
   334  }
   335  
   336  func (clnt *client) newContainer(dir string, options ...CreateOption) *container {
   337  	container := &container{
   338  		containerCommon: containerCommon{
   339  			process: process{
   340  				dir: dir,
   341  				processCommon: processCommon{
   342  					containerID:  filepath.Base(dir),
   343  					client:       clnt,
   344  					friendlyName: InitFriendlyName,
   345  				},
   346  			},
   347  			processes: make(map[string]*process),
   348  		},
   349  	}
   350  	for _, option := range options {
   351  		if err := option.Apply(container); err != nil {
   352  			logrus.Error(err)
   353  		}
   354  	}
   355  	return container
   356  }
   357  
   358  func (clnt *client) UpdateResources(containerID string, resources Resources) error {
   359  	clnt.lock(containerID)
   360  	defer clnt.unlock(containerID)
   361  	container, err := clnt.getContainer(containerID)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	if container.systemPid == 0 {
   366  		return fmt.Errorf("No active process for container %s", containerID)
   367  	}
   368  	_, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{
   369  		Id:        containerID,
   370  		Pid:       InitFriendlyName,
   371  		Resources: (*containerd.UpdateResource)(&resources),
   372  	})
   373  	if err != nil {
   374  		return err
   375  	}
   376  	return nil
   377  }
   378  
   379  func (clnt *client) getExitNotifier(containerID string) *exitNotifier {
   380  	clnt.mapMutex.RLock()
   381  	defer clnt.mapMutex.RUnlock()
   382  	return clnt.exitNotifiers[containerID]
   383  }
   384  
   385  func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier {
   386  	clnt.mapMutex.Lock()
   387  	w, ok := clnt.exitNotifiers[containerID]
   388  	defer clnt.mapMutex.Unlock()
   389  	if !ok {
   390  		w = &exitNotifier{c: make(chan struct{}), client: clnt}
   391  		clnt.exitNotifiers[containerID] = w
   392  	}
   393  	return w
   394  }
   395  
   396  func (clnt *client) restore(cont *containerd.Container, options ...CreateOption) (err error) {
   397  	clnt.lock(cont.Id)
   398  	defer clnt.unlock(cont.Id)
   399  
   400  	logrus.Debugf("restore container %s state %s", cont.Id, cont.Status)
   401  
   402  	containerID := cont.Id
   403  	if _, err := clnt.getContainer(containerID); err == nil {
   404  		return fmt.Errorf("container %s is already active", containerID)
   405  	}
   406  
   407  	defer func() {
   408  		if err != nil {
   409  			clnt.deleteContainer(cont.Id)
   410  		}
   411  	}()
   412  
   413  	container := clnt.newContainer(cont.BundlePath, options...)
   414  	container.systemPid = systemPid(cont)
   415  
   416  	var terminal bool
   417  	for _, p := range cont.Processes {
   418  		if p.Pid == InitFriendlyName {
   419  			terminal = p.Terminal
   420  		}
   421  	}
   422  
   423  	iopipe, err := container.openFifos(terminal)
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	if err := clnt.backend.AttachStreams(containerID, *iopipe); err != nil {
   429  		return err
   430  	}
   431  
   432  	clnt.appendContainer(container)
   433  
   434  	err = clnt.backend.StateChanged(containerID, StateInfo{
   435  		CommonStateInfo: CommonStateInfo{
   436  			State: StateRestore,
   437  			Pid:   container.systemPid,
   438  		}})
   439  
   440  	if err != nil {
   441  		return err
   442  	}
   443  
   444  	if event, ok := clnt.remote.pastEvents[containerID]; ok {
   445  		// This should only be a pause or resume event
   446  		if event.Type == StatePause || event.Type == StateResume {
   447  			return clnt.backend.StateChanged(containerID, StateInfo{
   448  				CommonStateInfo: CommonStateInfo{
   449  					State: event.Type,
   450  					Pid:   container.systemPid,
   451  				}})
   452  		}
   453  
   454  		logrus.Warnf("unexpected backlog event: %#v", event)
   455  	}
   456  
   457  	return nil
   458  }
   459  
   460  func (clnt *client) Restore(containerID string, options ...CreateOption) error {
   461  	if clnt.liveRestore {
   462  		cont, err := clnt.getContainerdContainer(containerID)
   463  		if err == nil && cont.Status != "stopped" {
   464  			if err := clnt.restore(cont, options...); err != nil {
   465  				logrus.Errorf("error restoring %s: %v", containerID, err)
   466  			}
   467  			return nil
   468  		}
   469  		return clnt.setExited(containerID)
   470  	}
   471  
   472  	cont, err := clnt.getContainerdContainer(containerID)
   473  	if err == nil && cont.Status != "stopped" {
   474  		w := clnt.getOrCreateExitNotifier(containerID)
   475  		clnt.lock(cont.Id)
   476  		container := clnt.newContainer(cont.BundlePath)
   477  		container.systemPid = systemPid(cont)
   478  		clnt.appendContainer(container)
   479  		clnt.unlock(cont.Id)
   480  
   481  		container.discardFifos()
   482  
   483  		if err := clnt.Signal(containerID, int(syscall.SIGTERM)); err != nil {
   484  			logrus.Errorf("error sending sigterm to %v: %v", containerID, err)
   485  		}
   486  		select {
   487  		case <-time.After(10 * time.Second):
   488  			if err := clnt.Signal(containerID, int(syscall.SIGKILL)); err != nil {
   489  				logrus.Errorf("error sending sigkill to %v: %v", containerID, err)
   490  			}
   491  			select {
   492  			case <-time.After(2 * time.Second):
   493  			case <-w.wait():
   494  				return nil
   495  			}
   496  		case <-w.wait():
   497  			return nil
   498  		}
   499  	}
   500  
   501  	clnt.deleteContainer(containerID)
   502  
   503  	return clnt.setExited(containerID)
   504  }
   505  
   506  type exitNotifier struct {
   507  	id     string
   508  	client *client
   509  	c      chan struct{}
   510  	once   sync.Once
   511  }
   512  
   513  func (en *exitNotifier) close() {
   514  	en.once.Do(func() {
   515  		close(en.c)
   516  		en.client.mapMutex.Lock()
   517  		if en == en.client.exitNotifiers[en.id] {
   518  			delete(en.client.exitNotifiers, en.id)
   519  		}
   520  		en.client.mapMutex.Unlock()
   521  	})
   522  }
   523  func (en *exitNotifier) wait() <-chan struct{} {
   524  	return en.c
   525  }