Merge pull request #2686 from jterry75/runhcs_improvements

Various containerd-shim-runhcs bug fixes/improvements
This commit is contained in:
Michael Crosby 2018-09-28 11:19:54 -04:00 committed by GitHub
commit 655ba65875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 352 additions and 56 deletions

View File

@ -31,6 +31,7 @@ import (
"github.com/containerd/containerd/runtime/v2/task" "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc" "github.com/containerd/ttrpc"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
func shimBinary(ctx context.Context, bundle *Bundle, runtime, containerdAddress string, events *exchange.Exchange, rt *runtime.TaskList) *binary { func shimBinary(ctx context.Context, bundle *Bundle, runtime, containerdAddress string, events *exchange.Exchange, rt *runtime.TaskList) *binary {
@ -52,13 +53,18 @@ type binary struct {
} }
func (b *binary) Start(ctx context.Context) (_ *shim, err error) { func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
args := []string{"-id", b.bundle.ID}
if logrus.GetLevel() == logrus.DebugLevel {
args = append(args, "-debug")
}
args = append(args, "start")
cmd, err := client.Command( cmd, err := client.Command(
ctx, ctx,
b.runtime, b.runtime,
b.containerdAddress, b.containerdAddress,
b.bundle.Path, b.bundle.Path,
"-id", b.bundle.ID, args...,
"start",
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -22,15 +22,19 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil"
"net"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/cmd/go-runhcs" "github.com/Microsoft/hcsshim/cmd/go-runhcs"
containerd_types "github.com/containerd/containerd/api/types" containerd_types "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/api/types/task" "github.com/containerd/containerd/api/types/task"
@ -50,24 +54,87 @@ import (
const ( const (
runhcsShimVersion = "0.0.1" runhcsShimVersion = "0.0.1"
safePipePrefix = `\\.\pipe\ProtectedPrefix\Administrators\`
errorConnectionAborted syscall.Errno = 1236
) )
var ( var (
empty = &ptypes.Empty{} empty = &ptypes.Empty{}
) )
func newRunhcs(bundle string) *runhcs.Runhcs { func newRunhcs(debugLog string) *runhcs.Runhcs {
rhs := &runhcs.Runhcs{ rhs := &runhcs.Runhcs{
Debug: logrus.GetLevel() == logrus.DebugLevel, Debug: debugLog != "",
LogFormat: runhcs.JSON, LogFormat: runhcs.JSON,
Owner: "containerd-runhcs-shim-v1", Owner: "containerd-runhcs-shim-v1",
} }
if rhs.Debug { if rhs.Debug {
rhs.Log = filepath.Join(bundle, "runhcs-debug.log") rhs.Log = debugLog
} }
return rhs return rhs
} }
// forwardRunhcsLogs copies logs from c and writes them to the ctx logger
// upstream.
func forwardRunhcsLogs(ctx context.Context, c net.Conn, fields logrus.Fields) {
defer c.Close()
j := json.NewDecoder(c)
for {
e := logrus.Entry{}
err := j.Decode(&e.Data)
if err == io.EOF || err == errorConnectionAborted {
break
}
if err != nil {
// Likely the last message wasn't complete at closure. Just read all
// data and forward as error.
data, _ := ioutil.ReadAll(io.MultiReader(j.Buffered(), c))
if len(data) != 0 {
log.G(ctx).WithFields(fields).Error(string(data))
}
break
}
msg := e.Data[logrus.FieldKeyMsg]
delete(e.Data, logrus.FieldKeyMsg)
level, err := logrus.ParseLevel(e.Data[logrus.FieldKeyLevel].(string))
if err != nil {
log.G(ctx).WithFields(fields).WithError(err).Debug("invalid log level")
level = logrus.DebugLevel
}
delete(e.Data, logrus.FieldKeyLevel)
// TODO: JTERRY75 maybe we need to make this configurable so we know
// that runhcs is using the same one we are deserializing.
ti, err := time.Parse(logrus.DefaultTimestampFormat, e.Data[logrus.FieldKeyTime].(string))
if err != nil {
log.G(ctx).WithFields(fields).WithError(err).Debug("invalid time stamp format")
ti = time.Time{}
}
delete(e.Data, logrus.FieldKeyTime)
etr := log.G(ctx).WithFields(fields).WithFields(e.Data)
etr.Time = ti
switch level {
case logrus.PanicLevel:
etr.Panic(msg)
case logrus.FatalLevel:
etr.Fatal(msg)
case logrus.ErrorLevel:
etr.Error(msg)
case logrus.WarnLevel:
etr.Warn(msg)
case logrus.InfoLevel:
etr.Info(msg)
case logrus.DebugLevel:
etr.Debug(msg)
}
}
}
// New returns a new runhcs shim service that can be used via GRPC // New returns a new runhcs shim service that can be used via GRPC
func New(ctx context.Context, id string, publisher events.Publisher) (shim.Shim, error) { func New(ctx context.Context, id string, publisher events.Publisher) (shim.Shim, error) {
return &service{ return &service{
@ -87,6 +154,10 @@ type service struct {
context context.Context context context.Context
// debugLog if not "" indicates the log pipe path for runhcs.exe to write its logs to.
debugLog string
debugListener net.Listener
id string id string
processes map[string]*process processes map[string]*process
@ -160,8 +231,13 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
"-address", containerdAddress, "-address", containerdAddress,
"-publish-binary", containerdBinary, "-publish-binary", containerdBinary,
"-socket", socketAddress, "-socket", socketAddress,
"-debug",
} }
opts, ok := ctx.Value(shim.OptsKey{}).(shim.Opts)
if ok && opts.Debug {
args = append(args, "-debug")
}
cmd := exec.Command(self, args...) cmd := exec.Command(self, args...)
cmd.Dir = cwd cmd.Dir = cwd
cmd.Env = append(os.Environ(), "GOMAXPROCS=2") cmd.Env = append(os.Environ(), "GOMAXPROCS=2")
@ -190,6 +266,8 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
// State returns runtime state information for a process // State returns runtime state information for a process
func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.StateResponse, error) { func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.StateResponse, error) {
log.G(ctx).Debugf("State: %s: %s", r.ID, r.ExecID)
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -199,14 +277,26 @@ func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.
return nil, err return nil, err
} }
rhcs := newRunhcs(p.bundle) var tstatus string
cs, err := rhcs.State(ctx, p.id)
if err != nil { // This is a container
return nil, err if p.cid == p.id {
rhcs := newRunhcs(s.debugLog)
cs, err := rhcs.State(ctx, p.id)
if err != nil {
return nil, err
}
tstatus = cs.Status
} else {
if p.started {
tstatus = "running"
} else {
tstatus = "created"
}
} }
status := task.StatusUnknown status := task.StatusUnknown
switch cs.Status { switch tstatus {
case "created": case "created":
status = task.StatusCreated status = task.StatusCreated
case "running": case "running":
@ -219,8 +309,8 @@ func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.
pe := p.stat() pe := p.stat()
return &taskAPI.StateResponse{ return &taskAPI.StateResponse{
ID: p.id, ID: p.id,
Bundle: cs.Bundle, Bundle: p.bundle,
Pid: uint32(cs.InitProcessPid), Pid: p.pid,
Status: status, Status: status,
Stdin: p.stdin, Stdin: p.stdin,
Stdout: p.stdout, Stdout: p.stdout,
@ -301,7 +391,7 @@ func writeMountsToConfig(bundle string, mounts []*containerd_types.Mount) error
// Create a new initial process and container with runhcs // Create a new initial process and container with runhcs
func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) { func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*taskAPI.CreateTaskResponse, error) {
log.G(ctx).Infof("Create: %s", r.ID) log.G(ctx).Debugf("Create: %s", r.ID)
// Hold the lock for the entire duration to avoid duplicate process creation. // Hold the lock for the entire duration to avoid duplicate process creation.
s.mu.Lock() s.mu.Lock()
@ -346,16 +436,84 @@ func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*ta
pr.close() pr.close()
} }
}() }()
// TODO: Parse the real RunHcs Opts r.Options
opts, ok := ctx.Value(shim.OptsKey{}).(shim.Opts)
if ok && opts.Debug {
if s.debugLog == "" {
logPath := safePipePrefix + fmt.Sprintf("runhcs-log-%s", r.ID)
l, err := winio.ListenPipe(logPath, nil)
if err != nil {
return nil, err
}
s.debugLog = logPath
s.debugListener = l
// Accept connections and forward all logs for each runhcs.exe
// invocation
go func() {
for {
c, err := s.debugListener.Accept()
if err != nil {
if err == errorConnectionAborted {
break
}
log.G(ctx).WithError(err).Debug("log accept failure")
// Logrus error locally?
continue
}
fields := map[string]interface{}{
"log-source": "runhcs",
"task-id": r.ID,
}
go forwardRunhcsLogs(ctx, c, fields)
}
}()
}
}
pidfilePath := path.Join(r.Bundle, "runhcs-pidfile.pid") pidfilePath := path.Join(r.Bundle, "runhcs-pidfile.pid")
copts := &runhcs.CreateOpts{ copts := &runhcs.CreateOpts{
IO: io, IO: io,
PidFile: pidfilePath, PidFile: pidfilePath,
ShimLog: path.Join(r.Bundle, "runhcs-shim.log"),
VMLog: path.Join(r.Bundle, "runhcs-vm-shim.log"),
} }
rhcs := newRunhcs(s.debugLog)
if rhcs.Debug {
doForwardLogs := func(source, logPipeFmt string, opt *string) error {
pipeName := fmt.Sprintf(logPipeFmt, r.ID)
*opt = safePipePrefix + pipeName
l, err := winio.ListenPipe(*opt, nil)
if err != nil {
return err
}
go func() {
defer l.Close()
c, err := l.Accept()
if err != nil {
log.G(ctx).
WithField("task-id", r.ID).
WithError(err).
Errorf("failed to accept %s", pipeName)
} else {
fields := map[string]interface{}{
"log-source": source,
"task-id": r.ID,
}
go forwardRunhcsLogs(ctx, c, fields)
}
}()
return nil
}
rhcs := newRunhcs(r.Bundle) err = doForwardLogs("runhcs-shim", "runhcs-shim-log-%s", &copts.ShimLog)
if err != nil {
return nil, err
}
err = doForwardLogs("runhcs--vm-shim", "runhcs-vm-shim-log-%s", &copts.VMLog)
if err != nil {
return nil, err
}
}
err = rhcs.Create(ctx, r.ID, r.Bundle, copts) err = rhcs.Create(ctx, r.ID, r.Bundle, copts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -391,7 +549,7 @@ func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (*ta
// Start a process // Start a process
func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) { func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) {
log.G(ctx).Infof("Start: %s: %s", r.ID, r.ExecID) log.G(ctx).Debugf("Start: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
if p, err = s.getProcess(r.ID, r.ExecID); err != nil { if p, err = s.getProcess(r.ID, r.ExecID); err != nil {
@ -404,7 +562,7 @@ func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.
return nil, errors.New("cannot start already started container or process") return nil, errors.New("cannot start already started container or process")
} }
rhcs := newRunhcs(p.bundle) rhcs := newRunhcs(s.debugLog)
// This is a start/exec // This is a start/exec
if r.ExecID != "" { if r.ExecID != "" {
@ -414,10 +572,43 @@ func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.
eopts := &runhcs.ExecOpts{ eopts := &runhcs.ExecOpts{
IO: p.relay.io, IO: p.relay.io,
PidFile: pidfilePath, PidFile: pidfilePath,
ShimLog: path.Join(p.bundle, fmt.Sprintf("runhcs-%s-shim.log", execFmt)),
Detach: true, Detach: true,
} }
if rhcs.Debug {
doForwardLogs := func(source, pipeName string, opt *string) error {
*opt = safePipePrefix + pipeName
l, err := winio.ListenPipe(*opt, nil)
if err != nil {
return err
}
go func() {
defer l.Close()
c, err := l.Accept()
if err != nil {
log.G(ctx).
WithField("task-id", r.ID).
WithField("exec-id", r.ExecID).
WithError(err).
Errorf("failed to accept %s", pipeName)
} else {
fields := map[string]interface{}{
"log-source": source,
"task-id": r.ID,
"exec-id": r.ExecID,
}
go forwardRunhcsLogs(ctx, c, fields)
}
}()
return nil
}
err = doForwardLogs("runhcs-shim-exec", "runhcs-shim-log-"+execFmt, &eopts.ShimLog)
if err != nil {
return nil, err
}
}
// ID here is the containerID to exec the process in. // ID here is the containerID to exec the process in.
err = rhcs.Exec(ctx, r.ID, procConfig, eopts) err = rhcs.Exec(ctx, r.ID, procConfig, eopts)
if err != nil { if err != nil {
@ -473,7 +664,7 @@ func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.
// Delete the initial process and container // Delete the initial process and container
func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) { func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) {
log.G(ctx).Infof("Delete: %s: %s", r.ID, r.ExecID) log.G(ctx).Debugf("Delete: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
@ -481,13 +672,15 @@ func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAP
return nil, err return nil, err
} }
rhs := newRunhcs(p.bundle) // This is a container
if p.cid == p.id {
dopts := &runhcs.DeleteOpts{ rhs := newRunhcs(s.debugLog)
Force: true, dopts := &runhcs.DeleteOpts{
} Force: true,
if err := rhs.Delete(ctx, p.id, dopts); err != nil { }
return nil, errors.Wrapf(err, "failed to delete container: %s", p.id) if err := rhs.Delete(ctx, p.id, dopts); err != nil {
return nil, errors.Wrapf(err, "failed to delete container: %s", p.id)
}
} }
select { select {
@ -511,11 +704,15 @@ func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAP
// Pids returns all pids inside the container // Pids returns all pids inside the container
func (s *service) Pids(ctx context.Context, r *taskAPI.PidsRequest) (*taskAPI.PidsResponse, error) { func (s *service) Pids(ctx context.Context, r *taskAPI.PidsRequest) (*taskAPI.PidsResponse, error) {
log.G(ctx).Debugf("Pids: %s", r.ID)
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }
// Pause the container // Pause the container
func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.Empty, error) { func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("Pause: %s", r.ID)
// TODO: Validate that 'id' is actually a valid parent container ID // TODO: Validate that 'id' is actually a valid parent container ID
var p *process var p *process
var err error var err error
@ -523,7 +720,7 @@ func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.E
return nil, err return nil, err
} }
rhcs := newRunhcs(p.bundle) rhcs := newRunhcs(s.debugLog)
if err = rhcs.Pause(ctx, p.id); err != nil { if err = rhcs.Pause(ctx, p.id); err != nil {
return nil, err return nil, err
} }
@ -533,6 +730,8 @@ func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.E
// Resume the container // Resume the container
func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes.Empty, error) { func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("Resume: %s", r.ID)
// TODO: Validate that 'id' is actually a valid parent container ID // TODO: Validate that 'id' is actually a valid parent container ID
var p *process var p *process
var err error var err error
@ -540,7 +739,7 @@ func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes
return nil, err return nil, err
} }
rhcs := newRunhcs(p.bundle) rhcs := newRunhcs(s.debugLog)
if err = rhcs.Resume(ctx, p.id); err != nil { if err = rhcs.Resume(ctx, p.id); err != nil {
return nil, err return nil, err
} }
@ -550,12 +749,14 @@ func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes
// Checkpoint the container // Checkpoint the container
func (s *service) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) (*ptypes.Empty, error) { func (s *service) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("Checkpoint: %s", r.ID)
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }
// Kill a process with the provided signal // Kill a process with the provided signal
func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Empty, error) { func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Empty, error) {
log.G(ctx).Infof("Kill: %s: %s", r.ID, r.ExecID) log.G(ctx).Debugf("Kill: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
@ -565,7 +766,7 @@ func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Emp
// TODO: JTERRY75 runhcs needs r.Signal in string form // TODO: JTERRY75 runhcs needs r.Signal in string form
// TODO: JTERRY75 runhcs support for r.All? // TODO: JTERRY75 runhcs support for r.All?
rhcs := newRunhcs(p.bundle) rhcs := newRunhcs(s.debugLog)
if err = rhcs.Kill(ctx, p.id, strconv.FormatUint(uint64(r.Signal), 10)); err != nil { if err = rhcs.Kill(ctx, p.id, strconv.FormatUint(uint64(r.Signal), 10)); err != nil {
if !strings.Contains(err.Error(), "container is stopped") { if !strings.Contains(err.Error(), "container is stopped") {
return nil, err return nil, err
@ -577,7 +778,7 @@ func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Emp
// Exec an additional process inside the container // Exec an additional process inside the container
func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*ptypes.Empty, error) { func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*ptypes.Empty, error) {
log.G(ctx).Infof("Exec: %s: %s", r.ID, r.ExecID) log.G(ctx).Debugf("Exec: %s: %s", r.ID, r.ExecID)
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -660,6 +861,8 @@ func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*pty
// ResizePty of a process // ResizePty of a process
func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*ptypes.Empty, error) { func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("ResizePty: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
if p, err = s.getProcess(r.ID, r.ExecID); err != nil { if p, err = s.getProcess(r.ID, r.ExecID); err != nil {
@ -670,7 +873,7 @@ func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*
opts := runhcs.ResizeTTYOpts{ opts := runhcs.ResizeTTYOpts{
Pid: &pid, Pid: &pid,
} }
rhcs := newRunhcs(p.bundle) rhcs := newRunhcs(s.debugLog)
if err = rhcs.ResizeTTY(ctx, p.cid, uint16(r.Width), uint16(r.Height), &opts); err != nil { if err = rhcs.ResizeTTY(ctx, p.cid, uint16(r.Width), uint16(r.Height), &opts); err != nil {
return nil, err return nil, err
} }
@ -680,6 +883,8 @@ func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*
// CloseIO of a process // CloseIO of a process
func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptypes.Empty, error) { func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("CloseIO: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
if p, err = s.getProcess(r.ID, r.ExecID); err != nil { if p, err = s.getProcess(r.ID, r.ExecID); err != nil {
@ -691,11 +896,15 @@ func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptyp
// Update a running container // Update a running container
func (s *service) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) (*ptypes.Empty, error) { func (s *service) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) (*ptypes.Empty, error) {
log.G(ctx).Debugf("Update: %s", r.ID)
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }
// Wait for a process to exit // Wait for a process to exit
func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.WaitResponse, error) { func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.WaitResponse, error) {
log.G(ctx).Debugf("Wait: %s: %s", r.ID, r.ExecID)
var p *process var p *process
var err error var err error
if p, err = s.getProcess(r.ID, r.ExecID); err != nil { if p, err = s.getProcess(r.ID, r.ExecID); err != nil {
@ -710,11 +919,15 @@ func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.Wa
// Stats returns statistics about the running container // Stats returns statistics about the running container
func (s *service) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) { func (s *service) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) {
log.G(ctx).Debugf("Stats: %s", r.ID)
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }
// Connect returns the runhcs shim information // Connect returns the runhcs shim information
func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*taskAPI.ConnectResponse, error) { func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*taskAPI.ConnectResponse, error) {
log.G(ctx).Debugf("Connect: %s", r.ID)
return &taskAPI.ConnectResponse{ return &taskAPI.ConnectResponse{
ShimPid: uint32(os.Getpid()), ShimPid: uint32(os.Getpid()),
TaskPid: s.processes[s.id].pid, TaskPid: s.processes[s.id].pid,
@ -724,7 +937,11 @@ func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*task
// Shutdown stops this instance of the runhcs shim // Shutdown stops this instance of the runhcs shim
func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) { func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) {
log.G(ctx).Infof("Shutdown: %s", r.ID) log.G(ctx).Debugf("Shutdown: %s", r.ID)
if s.debugListener != nil {
s.debugListener.Close()
}
os.Exit(0) os.Exit(0)
return empty, nil return empty, nil

View File

@ -53,6 +53,14 @@ type Shim interface {
StartShim(ctx context.Context, id, containerdBinary, containerdAddress string) (string, error) StartShim(ctx context.Context, id, containerdBinary, containerdAddress string) (string, error)
} }
// OptsKey is the context key for the Opts value.
type OptsKey struct{}
// Opts are context options associated with the shim invocation.
type Opts struct {
Debug bool
}
var ( var (
debugFlag bool debugFlag bool
idFlag string idFlag string
@ -133,6 +141,7 @@ func run(id string, initFunc Init) error {
return fmt.Errorf("shim namespace cannot be empty") return fmt.Errorf("shim namespace cannot be empty")
} }
ctx := namespaces.WithNamespace(context.Background(), namespaceFlag) ctx := namespaces.WithNamespace(context.Background(), namespaceFlag)
ctx = context.WithValue(ctx, OptsKey{}, Opts{Debug: debugFlag})
ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", id)) ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", id))
service, err := initFunc(ctx, idFlag, publisher) service, err := initFunc(ctx, idFlag, publisher)

View File

@ -0,0 +1,39 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package shim
import (
"context"
"testing"
)
func TestShimOptWithValue(t *testing.T) {
ctx := context.TODO()
ctx = context.WithValue(ctx, OptsKey{}, Opts{Debug: true})
o := ctx.Value(OptsKey{})
if o == nil {
t.Fatal("opts nil")
}
op, ok := o.(Opts)
if !ok {
t.Fatal("opts not of type Opts")
}
if !op.Debug {
t.Fatal("opts.Debug should be true")
}
}

View File

@ -33,7 +33,7 @@ golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio v0.4.10 github.com/Microsoft/go-winio v0.4.10
github.com/Microsoft/hcsshim v0.7.4 github.com/Microsoft/hcsshim v0.7.6
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a

View File

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings"
"sync" "sync"
"github.com/containerd/go-runc" "github.com/containerd/go-runc"
@ -21,7 +23,8 @@ const (
// JSON is the JSON formatted log output. // JSON is the JSON formatted log output.
JSON Format = "json" JSON Format = "json"
command = "runhcs" command = "runhcs"
safePipePrefix = `\\.\pipe\ProtectedPrefix\Administrators\`
) )
var bytesBufferPool = sync.Pool{ var bytesBufferPool = sync.Pool{
@ -43,7 +46,7 @@ func putBuf(b *bytes.Buffer) {
type Runhcs struct { type Runhcs struct {
// Debug enables debug output for logging. // Debug enables debug output for logging.
Debug bool Debug bool
// Log sets the log file path where internal debug information is written. // Log sets the log file path or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-log) where internal debug information is written.
Log string Log string
// LogFormat sets the format used by logs. // LogFormat sets the format used by logs.
LogFormat Format LogFormat Format
@ -59,8 +62,14 @@ func (r *Runhcs) args() []string {
out = append(out, "--debug") out = append(out, "--debug")
} }
if r.Log != "" { if r.Log != "" {
// TODO: JTERRY75 - Should we do abs here? if strings.HasPrefix(r.Log, safePipePrefix) {
out = append(out, "--log", r.Log) out = append(out, "--log", r.Log)
} else {
abs, err := filepath.Abs(r.Log)
if err == nil {
out = append(out, "--log", abs)
}
}
} }
if r.LogFormat != none { if r.LogFormat != none {
out = append(out, "--log-format", string(r.LogFormat)) out = append(out, "--log-format", string(r.LogFormat))

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
runc "github.com/containerd/go-runc" runc "github.com/containerd/go-runc"
) )
@ -13,9 +14,9 @@ type CreateOpts struct {
runc.IO runc.IO
// PidFile is the path to the file to write the process id to. // PidFile is the path to the file to write the process id to.
PidFile string PidFile string
// ShimLog is the path to the log file for the launched shim process. // ShimLog is the path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-shim-log) for the launched shim process.
ShimLog string ShimLog string
// VMLog is the path to the log file for the launched VM shim process. // VMLog is the path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-vm-log) for the launched VM shim process.
VMLog string VMLog string
// VMConsole is the path to the pipe for the VM's console (e.g. \\.\pipe\debugpipe) // VMConsole is the path to the pipe for the VM's console (e.g. \\.\pipe\debugpipe)
VMConsole string VMConsole string
@ -31,18 +32,26 @@ func (opt *CreateOpts) args() ([]string, error) {
out = append(out, "--pid-file", abs) out = append(out, "--pid-file", abs)
} }
if opt.ShimLog != "" { if opt.ShimLog != "" {
abs, err := filepath.Abs(opt.ShimLog) if strings.HasPrefix(opt.ShimLog, safePipePrefix) {
if err != nil { out = append(out, "--shim-log", opt.ShimLog)
return nil, err } else {
abs, err := filepath.Abs(opt.ShimLog)
if err != nil {
return nil, err
}
out = append(out, "--shim-log", abs)
} }
out = append(out, "--shim-log", abs)
} }
if opt.VMLog != "" { if opt.VMLog != "" {
abs, err := filepath.Abs(opt.VMLog) if strings.HasPrefix(opt.VMLog, safePipePrefix) {
if err != nil { out = append(out, "--vm-log", opt.VMLog)
return nil, err } else {
abs, err := filepath.Abs(opt.VMLog)
if err != nil {
return nil, err
}
out = append(out, "--vm-log", abs)
} }
out = append(out, "--vm-log", abs)
} }
if opt.VMConsole != "" { if opt.VMConsole != "" {
out = append(out, "--vm-console", opt.VMConsole) out = append(out, "--vm-console", opt.VMConsole)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"github.com/containerd/go-runc" "github.com/containerd/go-runc"
) )
@ -15,7 +16,7 @@ type ExecOpts struct {
Detach bool Detach bool
// PidFile is the path to the file to write the process id to. // PidFile is the path to the file to write the process id to.
PidFile string PidFile string
// ShimLog is the path to the log file for the launched shim process. // ShimLog is the path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-<exec-id>-log) for the launched shim process.
ShimLog string ShimLog string
} }
@ -32,11 +33,15 @@ func (opt *ExecOpts) args() ([]string, error) {
out = append(out, "--pid-file", abs) out = append(out, "--pid-file", abs)
} }
if opt.ShimLog != "" { if opt.ShimLog != "" {
abs, err := filepath.Abs(opt.ShimLog) if strings.HasPrefix(opt.ShimLog, safePipePrefix) {
if err != nil { out = append(out, "--shim-log", opt.ShimLog)
return nil, err } else {
abs, err := filepath.Abs(opt.ShimLog)
if err != nil {
return nil, err
}
out = append(out, "--shim-log", abs)
} }
out = append(out, "--shim-log", abs)
} }
return out, nil return out, nil
} }

View File

@ -6,6 +6,8 @@ import (
// HNSEndpoint represents a network endpoint in HNS // HNSEndpoint represents a network endpoint in HNS
type HNSEndpoint = hns.HNSEndpoint type HNSEndpoint = hns.HNSEndpoint
// Namespace represents a Compartment.
type Namespace = hns.Namespace
//SystemType represents the type of the system on which actions are done //SystemType represents the type of the system on which actions are done
type SystemType string type SystemType string