...

Source file src/github.com/Microsoft/hcsshim/internal/guest/runtime/hcsv2/container.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/runtime/hcsv2

     1  //go:build linux
     2  // +build linux
     3  
     4  package hcsv2
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"sync"
    11  	"sync/atomic"
    12  	"syscall"
    13  
    14  	"github.com/containerd/cgroups"
    15  	v1 "github.com/containerd/cgroups/stats/v1"
    16  	oci "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  	"go.opencensus.io/trace"
    20  
    21  	"github.com/Microsoft/hcsshim/internal/guest/gcserr"
    22  	"github.com/Microsoft/hcsshim/internal/guest/prot"
    23  	"github.com/Microsoft/hcsshim/internal/guest/runtime"
    24  	specInternal "github.com/Microsoft/hcsshim/internal/guest/spec"
    25  	"github.com/Microsoft/hcsshim/internal/guest/stdio"
    26  	"github.com/Microsoft/hcsshim/internal/guest/storage"
    27  	"github.com/Microsoft/hcsshim/internal/guest/transport"
    28  	"github.com/Microsoft/hcsshim/internal/log"
    29  	"github.com/Microsoft/hcsshim/internal/logfields"
    30  	"github.com/Microsoft/hcsshim/internal/oc"
    31  	"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
    32  	"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
    33  )
    34  
    35  // containerStatus has been introduced to enable parallel container creation
    36  type containerStatus uint32
    37  
    38  const (
    39  	// containerCreating is the default status set on a Container object, when
    40  	// no underlying runtime container or init process has been assigned
    41  	containerCreating containerStatus = iota
    42  	// containerCreated is the status when a runtime container and init process
    43  	// have been assigned, but runtime start command has not been issued yet
    44  	containerCreated
    45  )
    46  
    47  type Container struct {
    48  	id    string
    49  	vsock transport.Transport
    50  
    51  	spec          *oci.Spec
    52  	ociBundlePath string
    53  	isSandbox     bool
    54  
    55  	container   runtime.Container
    56  	initProcess *containerProcess
    57  
    58  	etL      sync.Mutex
    59  	exitType prot.NotificationType
    60  
    61  	processesMutex sync.Mutex
    62  	processes      map[uint32]*containerProcess
    63  
    64  	// Only access atomically through getStatus/setStatus.
    65  	status containerStatus
    66  
    67  	// scratchDirPath represents the path inside the UVM where the scratch directory
    68  	// of this container is located. Usually, this is either `/run/gcs/c/<containerID>` or
    69  	// `/run/gcs/c/<UVMID>/container_<containerID>` if scratch is shared with UVM scratch.
    70  	scratchDirPath string
    71  }
    72  
    73  func (c *Container) Start(ctx context.Context, conSettings stdio.ConnectionSettings) (int, error) {
    74  	log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::Start")
    75  	stdioSet, err := stdio.Connect(c.vsock, conSettings)
    76  	if err != nil {
    77  		return -1, err
    78  	}
    79  	if c.initProcess.spec.Terminal {
    80  		ttyr := c.container.Tty()
    81  		ttyr.ReplaceConnectionSet(stdioSet)
    82  		ttyr.Start()
    83  	} else {
    84  		pr := c.container.PipeRelay()
    85  		pr.ReplaceConnectionSet(stdioSet)
    86  		pr.CloseUnusedPipes()
    87  		pr.Start()
    88  	}
    89  	err = c.container.Start()
    90  	if err != nil {
    91  		stdioSet.Close()
    92  	}
    93  	return int(c.initProcess.pid), err
    94  }
    95  
    96  func (c *Container) ExecProcess(ctx context.Context, process *oci.Process, conSettings stdio.ConnectionSettings) (int, error) {
    97  	log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::ExecProcess")
    98  	stdioSet, err := stdio.Connect(c.vsock, conSettings)
    99  	if err != nil {
   100  		return -1, err
   101  	}
   102  
   103  	// Add in the core rlimit specified on the container in case there was one set. This makes it so that execed processes can also generate
   104  	// core dumps.
   105  	process.Rlimits = c.spec.Process.Rlimits
   106  
   107  	// If the client provided a user for the container to run as, we want to have the exec run as this user as well
   108  	// unless the exec's spec was explicitly set to a different user. If the Username field is filled in on the containers
   109  	// spec, at this point that means the work to find a uid:gid pairing for this username has already been done, so simply
   110  	// assign the uid:gid from the container.
   111  	if process.User.Username != "" {
   112  		// The exec provided a user string of it's own. Grab the uid:gid pairing for the string (if one exists).
   113  		if err := setUserStr(&oci.Spec{Root: c.spec.Root, Process: process}, process.User.Username); err != nil {
   114  			return -1, err
   115  		}
   116  		// Runc doesn't care about this, and just to be safe clear it.
   117  		process.User.Username = ""
   118  	} else if c.spec.Process.User.Username != "" {
   119  		process.User = c.spec.Process.User
   120  	}
   121  
   122  	p, err := c.container.ExecProcess(process, stdioSet)
   123  	if err != nil {
   124  		stdioSet.Close()
   125  		return -1, err
   126  	}
   127  
   128  	pid := p.Pid()
   129  	c.processesMutex.Lock()
   130  	c.processes[uint32(pid)] = newProcess(c, process, p, uint32(pid), false)
   131  	c.processesMutex.Unlock()
   132  	return pid, nil
   133  }
   134  
   135  // InitProcess returns the container's init process
   136  func (c *Container) InitProcess() Process {
   137  	return c.initProcess
   138  }
   139  
   140  // GetProcess returns the Process with the matching 'pid'. If the 'pid' does
   141  // not exit returns error.
   142  func (c *Container) GetProcess(pid uint32) (Process, error) {
   143  	//todo: thread a context to this function call
   144  	logrus.WithFields(logrus.Fields{
   145  		logfields.ContainerID: c.id,
   146  		logfields.ProcessID:   pid,
   147  	}).Info("opengcs::Container::GetProcess")
   148  	if c.initProcess.pid == pid {
   149  		return c.initProcess, nil
   150  	}
   151  
   152  	c.processesMutex.Lock()
   153  	defer c.processesMutex.Unlock()
   154  
   155  	p, ok := c.processes[pid]
   156  	if !ok {
   157  		return nil, gcserr.NewHresultError(gcserr.HrErrNotFound)
   158  	}
   159  	return p, nil
   160  }
   161  
   162  // GetAllProcessPids returns all process pids in the container namespace.
   163  func (c *Container) GetAllProcessPids(ctx context.Context) ([]int, error) {
   164  	log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::GetAllProcessPids")
   165  	state, err := c.container.GetAllProcesses()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	pids := make([]int, len(state))
   170  	for i, s := range state {
   171  		pids[i] = s.Pid
   172  	}
   173  	return pids, nil
   174  }
   175  
   176  // Kill sends 'signal' to the container process.
   177  func (c *Container) Kill(ctx context.Context, signal syscall.Signal) error {
   178  	log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::Kill")
   179  	err := c.container.Kill(signal)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	c.setExitType(signal)
   184  	return nil
   185  }
   186  
   187  func (c *Container) Delete(ctx context.Context) error {
   188  	entity := log.G(ctx).WithField(logfields.ContainerID, c.id)
   189  	entity.Info("opengcs::Container::Delete")
   190  	if c.isSandbox {
   191  		// remove user mounts in sandbox container
   192  		if err := storage.UnmountAllInPath(ctx, specInternal.SandboxMountsDir(c.id), true); err != nil {
   193  			entity.WithError(err).Error("failed to unmount sandbox mounts")
   194  		}
   195  
   196  		// remove hugepages mounts in sandbox container
   197  		if err := storage.UnmountAllInPath(ctx, specInternal.HugePagesMountsDir(c.id), true); err != nil {
   198  			entity.WithError(err).Error("failed to unmount hugepages mounts")
   199  		}
   200  	}
   201  
   202  	var retErr error
   203  	if err := c.container.Delete(); err != nil {
   204  		retErr = err
   205  	}
   206  
   207  	if err := os.RemoveAll(c.scratchDirPath); err != nil {
   208  		if retErr != nil {
   209  			retErr = fmt.Errorf("errors deleting container state, %s & %s", retErr, err)
   210  		} else {
   211  			retErr = err
   212  		}
   213  	}
   214  
   215  	if err := os.RemoveAll(c.ociBundlePath); err != nil {
   216  		if retErr != nil {
   217  			retErr = fmt.Errorf("errors deleting container oci bundle dir, %s & %s", retErr, err)
   218  		} else {
   219  			retErr = err
   220  		}
   221  	}
   222  
   223  	return retErr
   224  }
   225  
   226  func (c *Container) Update(ctx context.Context, resources interface{}) error {
   227  	log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::Update")
   228  	return c.container.Update(resources)
   229  }
   230  
   231  // Wait waits for the container's init process to exit.
   232  func (c *Container) Wait() prot.NotificationType {
   233  	_, span := oc.StartSpan(context.Background(), "opengcs::Container::Wait")
   234  	defer span.End()
   235  	span.AddAttributes(trace.StringAttribute(logfields.ContainerID, c.id))
   236  
   237  	c.initProcess.writersWg.Wait()
   238  	c.etL.Lock()
   239  	defer c.etL.Unlock()
   240  	return c.exitType
   241  }
   242  
   243  // setExitType sets `c.exitType` to the appropriate value based on `signal` if
   244  // `signal` will take down the container.
   245  func (c *Container) setExitType(signal syscall.Signal) {
   246  	c.etL.Lock()
   247  	defer c.etL.Unlock()
   248  
   249  	if signal == syscall.SIGTERM {
   250  		c.exitType = prot.NtGracefulExit
   251  	} else if signal == syscall.SIGKILL {
   252  		c.exitType = prot.NtForcedExit
   253  	}
   254  }
   255  
   256  // GetStats returns the cgroup metrics for the container.
   257  func (c *Container) GetStats(ctx context.Context) (*v1.Metrics, error) {
   258  	_, span := oc.StartSpan(ctx, "opengcs::Container::GetStats")
   259  	defer span.End()
   260  	span.AddAttributes(trace.StringAttribute("cid", c.id))
   261  
   262  	cgroupPath := c.spec.Linux.CgroupsPath
   263  	cg, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(cgroupPath))
   264  	if err != nil {
   265  		return nil, errors.Errorf("failed to get container stats for %v: %v", c.id, err)
   266  	}
   267  
   268  	return cg.Stat(cgroups.IgnoreNotExist)
   269  }
   270  
   271  func (c *Container) modifyContainerConstraints(ctx context.Context, rt guestrequest.RequestType, cc *guestresource.LCOWContainerConstraints) (err error) {
   272  	return c.Update(ctx, cc.Linux)
   273  }
   274  
   275  func (c *Container) getStatus() containerStatus {
   276  	val := atomic.LoadUint32((*uint32)(&c.status))
   277  	return containerStatus(val)
   278  }
   279  
   280  func (c *Container) setStatus(st containerStatus) {
   281  	atomic.StoreUint32((*uint32)(&c.status), uint32(st))
   282  }
   283  
   284  func (c *Container) ID() string {
   285  	return c.id
   286  }
   287  

View as plain text