github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/worker/runtime/container.go (about)

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"regexp"
     8  	"time"
     9  
    10  	"code.cloudfoundry.org/garden"
    11  	"github.com/containerd/containerd"
    12  	"github.com/containerd/containerd/cio"
    13  	uuid "github.com/nu7hatch/gouuid"
    14  	"github.com/opencontainers/runtime-spec/specs-go"
    15  )
    16  
    17  const GraceTimeKey = "garden.grace-time"
    18  
    19  type UserNotFoundError struct {
    20  	User string
    21  }
    22  
    23  func (u UserNotFoundError) Error() string {
    24  	return fmt.Sprintf("user '%s' not found: no matching entries in /etc/passwd", u.User)
    25  }
    26  
    27  type Container struct {
    28  	container     containerd.Container
    29  	killer        Killer
    30  	rootfsManager RootfsManager
    31  }
    32  
    33  func NewContainer(
    34  	container containerd.Container,
    35  	killer Killer,
    36  	rootfsManager RootfsManager,
    37  ) *Container {
    38  	return &Container{
    39  		container:     container,
    40  		killer:        killer,
    41  		rootfsManager: rootfsManager,
    42  	}
    43  }
    44  
    45  var _ garden.Container = (*Container)(nil)
    46  
    47  func (c *Container) Handle() string {
    48  	return c.container.ID()
    49  }
    50  
    51  // Stop stops a container.
    52  //
    53  func (c *Container) Stop(kill bool) error {
    54  	ctx := context.Background()
    55  
    56  	task, err := c.container.Task(ctx, cio.Load)
    57  	if err != nil {
    58  		return fmt.Errorf("task lookup: %w", err)
    59  	}
    60  
    61  	behaviour := KillGracefully
    62  	if kill {
    63  		behaviour = KillUngracefully
    64  	}
    65  
    66  	err = c.killer.Kill(ctx, task, behaviour)
    67  	if err != nil {
    68  		return fmt.Errorf("kill: %w", err)
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  // Run a process inside the container.
    75  //
    76  func (c *Container) Run(
    77  	spec garden.ProcessSpec,
    78  	processIO garden.ProcessIO,
    79  ) (garden.Process, error) {
    80  	ctx := context.Background()
    81  
    82  	containerSpec, err := c.container.Spec(ctx)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("container spec: %w", err)
    85  	}
    86  
    87  	procSpec, err := c.setupContainerdProcSpec(spec, *containerSpec)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	err = c.rootfsManager.SetupCwd(containerSpec.Root.Path, procSpec.Cwd)
    93  	if err != nil {
    94  		return nil, fmt.Errorf("setup cwd: %w", err)
    95  	}
    96  
    97  	task, err := c.container.Task(ctx, nil)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("task retrieval: %w", err)
   100  	}
   101  
   102  	id := procID(spec)
   103  	cioOpts := containerdCIO(processIO, spec.TTY != nil)
   104  
   105  	proc, err := task.Exec(ctx, id, &procSpec, cio.NewCreator(cioOpts...))
   106  	if err != nil {
   107  		return nil, fmt.Errorf("task exec: %w", err)
   108  	}
   109  
   110  	exitStatusC, err := proc.Wait(ctx)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("proc wait: %w", err)
   113  	}
   114  
   115  	err = proc.Start(ctx)
   116  	if err != nil {
   117  		if isNoSuchExecutable(err) {
   118  			return nil, garden.ExecutableNotFoundError{Message: err.Error()}
   119  		}
   120  		return nil, fmt.Errorf("proc start: %w", err)
   121  	}
   122  
   123  	err = proc.CloseIO(ctx, containerd.WithStdinCloser)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("proc closeio: %w", err)
   126  	}
   127  
   128  	return NewProcess(proc, exitStatusC), nil
   129  }
   130  
   131  // Attach starts streaming the output back to the client from a specified process.
   132  //
   133  func (c *Container) Attach(pid string, processIO garden.ProcessIO) (process garden.Process, err error) {
   134  	ctx := context.Background()
   135  
   136  	if pid == "" {
   137  		return nil, ErrInvalidInput("empty pid")
   138  	}
   139  
   140  	task, err := c.container.Task(ctx, cio.Load)
   141  	if err != nil {
   142  		return nil, fmt.Errorf("task: %w", err)
   143  	}
   144  
   145  	cioOpts := []cio.Opt{
   146  		cio.WithStreams(
   147  			processIO.Stdin,
   148  			processIO.Stdout,
   149  			processIO.Stderr,
   150  		),
   151  	}
   152  
   153  	proc, err := task.LoadProcess(ctx, pid, cio.NewAttach(cioOpts...))
   154  	if err != nil {
   155  		return nil, fmt.Errorf("load proc: %w", err)
   156  	}
   157  
   158  	status, err := proc.Status(ctx)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("proc status: %w", err)
   161  	}
   162  
   163  	if status.Status != containerd.Running {
   164  		return nil, fmt.Errorf("proc not running: status = %s", status.Status)
   165  	}
   166  
   167  	exitStatusC, err := proc.Wait(ctx)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("proc wait: %w", err)
   170  	}
   171  
   172  	return NewProcess(proc, exitStatusC), nil
   173  }
   174  
   175  // Properties returns the current set of properties
   176  //
   177  func (c *Container) Properties() (garden.Properties, error) {
   178  	ctx := context.Background()
   179  
   180  	labels, err := c.container.Labels(ctx)
   181  	if err != nil {
   182  		return garden.Properties{}, fmt.Errorf("labels retrieval: %w", err)
   183  	}
   184  
   185  	return labels, nil
   186  }
   187  
   188  // Property returns the value of the property with the specified name.
   189  //
   190  func (c *Container) Property(name string) (string, error) {
   191  	properties, err := c.Properties()
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  
   196  	v, found := properties[name]
   197  	if !found {
   198  		return "", ErrNotFound(name)
   199  	}
   200  
   201  	return v, nil
   202  }
   203  
   204  // Set a named property on a container to a specified value.
   205  //
   206  func (c *Container) SetProperty(name string, value string) error {
   207  	labelSet := map[string]string{
   208  		name: value,
   209  	}
   210  
   211  	_, err := c.container.SetLabels(context.Background(), labelSet)
   212  	if err != nil {
   213  		return fmt.Errorf("set label: %w", err)
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // RemoveProperty - Not Implemented
   220  func (c *Container) RemoveProperty(name string) (err error) {
   221  	err = ErrNotImplemented
   222  	return
   223  }
   224  
   225  // Info - Not Implemented
   226  func (c *Container) Info() (info garden.ContainerInfo, err error) {
   227  	err = ErrNotImplemented
   228  	return
   229  }
   230  
   231  // Metrics - Not Implemented
   232  func (c *Container) Metrics() (metrics garden.Metrics, err error) {
   233  	err = ErrNotImplemented
   234  	return
   235  }
   236  
   237  // StreamIn - Not Implemented
   238  func (c *Container) StreamIn(spec garden.StreamInSpec) (err error) {
   239  	err = ErrNotImplemented
   240  	return
   241  }
   242  
   243  // StreamOut - Not Implemented
   244  func (c *Container) StreamOut(spec garden.StreamOutSpec) (readCloser io.ReadCloser, err error) {
   245  	err = ErrNotImplemented
   246  	return
   247  }
   248  
   249  // SetGraceTime stores the grace time as a containerd label with key "garden.grace-time"
   250  //
   251  func (c *Container) SetGraceTime(graceTime time.Duration) error {
   252  	err := c.SetProperty(GraceTimeKey, fmt.Sprintf("%d", graceTime))
   253  	if err != nil {
   254  		return fmt.Errorf("set grace time: %w", err)
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  // CurrentBandwidthLimits returns no limits (achieves parity with Guardian)
   261  func (c *Container) CurrentBandwidthLimits() (garden.BandwidthLimits, error) {
   262  	return garden.BandwidthLimits{}, nil
   263  }
   264  
   265  // CurrentCPULimits returns the CPU shares allocated to the container
   266  func (c *Container) CurrentCPULimits() (garden.CPULimits, error) {
   267  	spec, err := c.container.Spec(context.Background())
   268  	if err != nil {
   269  		return garden.CPULimits{}, err
   270  	}
   271  
   272  	if spec == nil ||
   273  		spec.Linux == nil ||
   274  		spec.Linux.Resources == nil ||
   275  		spec.Linux.Resources.CPU == nil ||
   276  		spec.Linux.Resources.CPU.Shares == nil {
   277  		return garden.CPULimits{}, nil
   278  	}
   279  
   280  	return garden.CPULimits{
   281  		Weight: *spec.Linux.Resources.CPU.Shares,
   282  	}, nil
   283  }
   284  
   285  // CurrentDiskLimits returns no limits (achieves parity with Guardian)
   286  func (c *Container) CurrentDiskLimits() (garden.DiskLimits, error) {
   287  	return garden.DiskLimits{}, nil
   288  }
   289  
   290  // CurrentMemoryLimits returns the memory limit in bytes allocated to the container
   291  func (c *Container) CurrentMemoryLimits() (limits garden.MemoryLimits, err error) {
   292  	spec, err := c.container.Spec(context.Background())
   293  	if err != nil {
   294  		return garden.MemoryLimits{}, err
   295  	}
   296  
   297  	if spec == nil ||
   298  		spec.Linux == nil ||
   299  		spec.Linux.Resources == nil ||
   300  		spec.Linux.Resources.Memory == nil ||
   301  		spec.Linux.Resources.Memory.Limit == nil {
   302  		return garden.MemoryLimits{}, nil
   303  	}
   304  
   305  	return garden.MemoryLimits{
   306  		LimitInBytes: uint64(*spec.Linux.Resources.Memory.Limit),
   307  	}, nil
   308  }
   309  
   310  // NetIn - Not Implemented
   311  func (c *Container) NetIn(hostPort, containerPort uint32) (a, b uint32, err error) {
   312  	err = ErrNotImplemented
   313  	return
   314  }
   315  
   316  // NetOut - Not Implemented
   317  func (c *Container) NetOut(netOutRule garden.NetOutRule) (err error) {
   318  	err = ErrNotImplemented
   319  	return
   320  }
   321  
   322  // BulkNetOut - Not Implemented
   323  func (c *Container) BulkNetOut(netOutRules []garden.NetOutRule) (err error) {
   324  	err = ErrNotImplemented
   325  	return
   326  }
   327  
   328  func procID(gdnProcSpec garden.ProcessSpec) string {
   329  	id := gdnProcSpec.ID
   330  	if id == "" {
   331  		uuid, err := uuid.NewV4()
   332  		if err != nil {
   333  			panic(fmt.Errorf("uuid gen: %w", err))
   334  		}
   335  
   336  		id = uuid.String()
   337  	}
   338  
   339  	return id
   340  }
   341  
   342  func (c *Container) setupContainerdProcSpec(gdnProcSpec garden.ProcessSpec, containerSpec specs.Spec) (specs.Process, error) {
   343  	procSpec := containerSpec.Process
   344  
   345  	procSpec.Args = append([]string{gdnProcSpec.Path}, gdnProcSpec.Args...)
   346  	procSpec.Env = append(procSpec.Env, gdnProcSpec.Env...)
   347  
   348  	cwd := gdnProcSpec.Dir
   349  	if cwd == "" {
   350  		cwd = "/"
   351  	}
   352  
   353  	procSpec.Cwd = cwd
   354  
   355  	if gdnProcSpec.TTY != nil {
   356  		procSpec.Terminal = true
   357  
   358  		if gdnProcSpec.TTY.WindowSize != nil {
   359  			procSpec.ConsoleSize = &specs.Box{
   360  				Width:  uint(gdnProcSpec.TTY.WindowSize.Columns),
   361  				Height: uint(gdnProcSpec.TTY.WindowSize.Rows),
   362  			}
   363  		}
   364  	}
   365  
   366  	if gdnProcSpec.User != "" {
   367  		var ok bool
   368  		var err error
   369  		procSpec.User, ok, err = c.rootfsManager.LookupUser(containerSpec.Root.Path, gdnProcSpec.User)
   370  		if err != nil {
   371  			return specs.Process{}, fmt.Errorf("lookup user: %w", err)
   372  		}
   373  		if !ok {
   374  			return specs.Process{}, UserNotFoundError{User: gdnProcSpec.User}
   375  		}
   376  
   377  		setUserEnv := fmt.Sprintf("USER=%s", gdnProcSpec.User)
   378  		procSpec.Env = append(procSpec.Env, setUserEnv)
   379  	}
   380  	return *procSpec, nil
   381  }
   382  
   383  func containerdCIO(gdnProcIO garden.ProcessIO, tty bool) []cio.Opt {
   384  	cioOpts := []cio.Opt{
   385  		cio.WithStreams(
   386  			gdnProcIO.Stdin,
   387  			gdnProcIO.Stdout,
   388  			gdnProcIO.Stderr,
   389  		),
   390  	}
   391  
   392  	if tty {
   393  		cioOpts = append(cioOpts, cio.WithTerminal)
   394  	}
   395  
   396  	return cioOpts
   397  }
   398  
   399  func isNoSuchExecutable(err error) bool {
   400  	noSuchFile := regexp.MustCompile(`starting container process caused: exec: .*: stat .*: no such file or directory`)
   401  	executableNotFound := regexp.MustCompile(`starting container process caused: exec: .*: executable file not found in \$PATH`)
   402  
   403  	return noSuchFile.MatchString(err.Error()) || executableNotFound.MatchString(err.Error())
   404  }