...

Source file src/github.com/Microsoft/hcsshim/internal/guest/bridge/bridge_v2.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/bridge

     1  //go:build linux
     2  // +build linux
     3  
     4  package bridge
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"syscall"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	"go.opencensus.io/trace"
    14  	"golang.org/x/sys/unix"
    15  
    16  	"github.com/Microsoft/hcsshim/internal/guest/commonutils"
    17  	"github.com/Microsoft/hcsshim/internal/guest/gcserr"
    18  	"github.com/Microsoft/hcsshim/internal/guest/prot"
    19  	"github.com/Microsoft/hcsshim/internal/guest/runtime/hcsv2"
    20  	"github.com/Microsoft/hcsshim/internal/guest/stdio"
    21  	"github.com/Microsoft/hcsshim/internal/log"
    22  	"github.com/Microsoft/hcsshim/internal/oc"
    23  	"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
    24  )
    25  
    26  // The capabilities of this GCS.
    27  var capabilities = prot.GcsCapabilities{
    28  	SendHostCreateMessage:   false,
    29  	SendHostStartMessage:    false,
    30  	HVSocketConfigOnStartup: false,
    31  	SupportedSchemaVersions: []prot.SchemaVersion{
    32  		{
    33  			Major: 2,
    34  			Minor: 1,
    35  		},
    36  	},
    37  	RuntimeOsType: prot.OsTypeLinux,
    38  	GuestDefinedCapabilities: prot.GcsGuestCapabilities{
    39  		NamespaceAddRequestSupported:  true,
    40  		SignalProcessSupported:        true,
    41  		DumpStacksSupported:           true,
    42  		DeleteContainerStateSupported: true,
    43  	},
    44  }
    45  
    46  // negotiateProtocolV2 was introduced in v4 so will not be called with a minimum
    47  // lower than that.
    48  func (b *Bridge) negotiateProtocolV2(r *Request) (_ RequestResponse, err error) {
    49  	_, span := oc.StartSpan(r.Context, "opengcs::bridge::negotiateProtocolV2")
    50  	defer span.End()
    51  	defer func() { oc.SetSpanStatus(span, err) }()
    52  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
    53  
    54  	var request prot.NegotiateProtocol
    55  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
    56  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
    57  	}
    58  
    59  	if request.MaximumVersion < uint32(prot.PvV4) || uint32(prot.PvMax) < request.MinimumVersion {
    60  		return nil, gcserr.NewHresultError(gcserr.HrVmcomputeUnsupportedProtocolVersion)
    61  	}
    62  
    63  	min := func(x, y uint32) uint32 {
    64  		if x < y {
    65  			return x
    66  		}
    67  		return y
    68  	}
    69  
    70  	major := min(uint32(prot.PvMax), request.MaximumVersion)
    71  
    72  	// Set our protocol selected version before return.
    73  	b.protVer = prot.ProtocolVersion(major)
    74  
    75  	return &prot.NegotiateProtocolResponse{
    76  		Version:      major,
    77  		Capabilities: capabilities,
    78  	}, nil
    79  }
    80  
    81  // createContainerV2 creates a container based on the settings passed in `r`.
    82  //
    83  // This is allowed only for protocol version 4+, schema version 2.1+
    84  func (b *Bridge) createContainerV2(r *Request) (_ RequestResponse, err error) {
    85  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::createContainerV2")
    86  	defer span.End()
    87  	defer func() { oc.SetSpanStatus(span, err) }()
    88  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
    89  
    90  	var request prot.ContainerCreate
    91  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
    92  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
    93  	}
    94  
    95  	var settingsV2 prot.VMHostedContainerSettingsV2
    96  	if err := commonutils.UnmarshalJSONWithHresult([]byte(request.ContainerConfig), &settingsV2); err != nil {
    97  		return nil, errors.Wrapf(err, "failed to unmarshal JSON for ContainerConfig \"%s\"", request.ContainerConfig)
    98  	}
    99  
   100  	if settingsV2.SchemaVersion.Cmp(prot.SchemaVersion{Major: 2, Minor: 1}) < 0 {
   101  		return nil, gcserr.WrapHresult(
   102  			errors.Errorf("invalid schema version: %v", settingsV2.SchemaVersion),
   103  			gcserr.HrVmcomputeInvalidJSON)
   104  	}
   105  
   106  	c, err := b.hostState.CreateContainer(ctx, request.ContainerID, &settingsV2)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	waitFn := func() prot.NotificationType {
   111  		return c.Wait()
   112  	}
   113  
   114  	go func() {
   115  		nt := waitFn()
   116  		notification := &prot.ContainerNotification{
   117  			MessageBase: prot.MessageBase{
   118  				ContainerID: request.ContainerID,
   119  				ActivityID:  request.ActivityID,
   120  			},
   121  			Type:       nt,
   122  			Operation:  prot.AoNone,
   123  			Result:     0,
   124  			ResultInfo: "",
   125  		}
   126  		b.PublishNotification(notification)
   127  	}()
   128  
   129  	return &prot.ContainerCreateResponse{}, nil
   130  }
   131  
   132  // startContainerV2 doesn't have a great correlation to LCOW. On Windows this is
   133  // used to start the container silo. In Linux the container is the process so we
   134  // wait until the exec process of the init process to actually issue the start.
   135  //
   136  // This is allowed only for protocol version 4+, schema version 2.1+
   137  func (b *Bridge) startContainerV2(r *Request) (_ RequestResponse, err error) {
   138  	_, span := oc.StartSpan(r.Context, "opengcs::bridge::startContainerV2")
   139  	defer span.End()
   140  	defer func() { oc.SetSpanStatus(span, err) }()
   141  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   142  
   143  	// This is just a noop, but needs to be handled so that an error isn't
   144  	// returned to the HCS.
   145  	var request prot.MessageBase
   146  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   147  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   148  	}
   149  
   150  	return &prot.MessageResponseBase{}, nil
   151  }
   152  
   153  // execProcessV2 is used to execute three types of processes in the guest.
   154  //
   155  // 1. HostProcess. This is a process in the Host pid namespace that runs as
   156  // root. It is signified by either `request.IsExternal` or `request.ContainerID
   157  // == hcsv2.UVMContainerID`.
   158  //
   159  // 2. Container Init process. This is the init process of the created container.
   160  // We use exec for this instead of `StartContainer` because the protocol does
   161  // not pass in the appropriate std pipes for relaying the results until exec.
   162  // Until this is called the container remains in the `created` state.
   163  //
   164  // 3. Container Exec process. This is a process that is run in the container's
   165  // pid namespace.
   166  //
   167  // This is allowed only for protocol version 4+, schema version 2.1+
   168  func (b *Bridge) execProcessV2(r *Request) (_ RequestResponse, err error) {
   169  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::execProcessV2")
   170  	defer span.End()
   171  	defer func() { oc.SetSpanStatus(span, err) }()
   172  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   173  
   174  	var request prot.ContainerExecuteProcess
   175  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   176  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   177  	}
   178  
   179  	// The request contains a JSON string field which is equivalent to an
   180  	// ExecuteProcessInfo struct.
   181  	var params prot.ProcessParameters
   182  	if err := commonutils.UnmarshalJSONWithHresult([]byte(request.Settings.ProcessParameters), &params); err != nil {
   183  		return nil, errors.Wrapf(err, "failed to unmarshal JSON for ProcessParameters \"%s\"", request.Settings.ProcessParameters)
   184  	}
   185  
   186  	var conSettings stdio.ConnectionSettings
   187  	if params.CreateStdInPipe {
   188  		conSettings.StdIn = &request.Settings.VsockStdioRelaySettings.StdIn
   189  	}
   190  	if params.CreateStdOutPipe {
   191  		conSettings.StdOut = &request.Settings.VsockStdioRelaySettings.StdOut
   192  	}
   193  	if params.CreateStdErrPipe {
   194  		conSettings.StdErr = &request.Settings.VsockStdioRelaySettings.StdErr
   195  	}
   196  
   197  	pid, err := b.hostState.ExecProcess(ctx, request.ContainerID, params, conSettings)
   198  
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	log.G(ctx).WithField("pid", pid).Debug("created process pid")
   203  	return &prot.ContainerExecuteProcessResponse{
   204  		ProcessID: uint32(pid),
   205  	}, nil
   206  }
   207  
   208  // killContainerV2 is a user forced terminate of the container and all processes
   209  // in the container. It is equivalent to sending SIGKILL to the init process and
   210  // all exec'd processes.
   211  //
   212  // This is allowed only for protocol version 4+, schema version 2.1+
   213  func (b *Bridge) killContainerV2(r *Request) (RequestResponse, error) {
   214  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::killContainerV2")
   215  	defer span.End()
   216  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   217  
   218  	return b.signalContainerShutdownV2(ctx, span, r, false)
   219  }
   220  
   221  // shutdownContainerV2 is a user requested shutdown of the container and all
   222  // processes in the container. It is equivalent to sending SIGTERM to the init
   223  // process and all exec'd processes.
   224  //
   225  // This is allowed only for protocol version 4+, schema version 2.1+
   226  func (b *Bridge) shutdownContainerV2(r *Request) (RequestResponse, error) {
   227  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::shutdownContainerV2")
   228  	defer span.End()
   229  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   230  
   231  	return b.signalContainerShutdownV2(ctx, span, r, true)
   232  }
   233  
   234  // signalContainerV2 is not a handler func. It is called from either
   235  // `killContainerV2` or `shutdownContainerV2` to deliver a SIGTERM or SIGKILL
   236  // respectively
   237  func (b *Bridge) signalContainerShutdownV2(ctx context.Context, span *trace.Span, r *Request, graceful bool) (_ RequestResponse, err error) {
   238  	defer func() { oc.SetSpanStatus(span, err) }()
   239  	span.AddAttributes(
   240  		trace.StringAttribute("cid", r.ContainerID),
   241  		trace.BoolAttribute("graceful", graceful),
   242  	)
   243  
   244  	var request prot.MessageBase
   245  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   246  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   247  	}
   248  
   249  	// If this is targeting the UVM send the request to the host itself.
   250  	if request.ContainerID == hcsv2.UVMContainerID {
   251  		// We are asking to shutdown the UVM itself.
   252  		// This is a destructive call. We do not respond to the HCS
   253  		b.quitChan <- true
   254  		b.hostState.Shutdown()
   255  	} else {
   256  		err = b.hostState.ShutdownContainer(ctx, request.ContainerID, graceful)
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  	}
   261  
   262  	return &prot.MessageResponseBase{}, nil
   263  }
   264  
   265  func (b *Bridge) signalProcessV2(r *Request) (_ RequestResponse, err error) {
   266  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::signalProcessV2")
   267  	defer span.End()
   268  	defer func() { oc.SetSpanStatus(span, err) }()
   269  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   270  
   271  	var request prot.ContainerSignalProcess
   272  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   273  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   274  	}
   275  
   276  	span.AddAttributes(
   277  		trace.Int64Attribute("pid", int64(request.ProcessID)),
   278  		trace.Int64Attribute("signal", int64(request.Options.Signal)))
   279  
   280  	var signal syscall.Signal
   281  	if request.Options.Signal == 0 {
   282  		signal = unix.SIGKILL
   283  	} else {
   284  		signal = syscall.Signal(request.Options.Signal)
   285  	}
   286  
   287  	if err := b.hostState.SignalContainerProcess(ctx, request.ContainerID, request.ProcessID, signal); err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	return &prot.MessageResponseBase{}, nil
   292  }
   293  
   294  func (b *Bridge) getPropertiesV2(r *Request) (_ RequestResponse, err error) {
   295  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::getPropertiesV2")
   296  	defer span.End()
   297  	defer func() { oc.SetSpanStatus(span, err) }()
   298  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   299  
   300  	var request prot.ContainerGetProperties
   301  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   302  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   303  	}
   304  
   305  	var query prot.PropertyQuery
   306  	if len(request.Query) != 0 {
   307  		if err := json.Unmarshal([]byte(request.Query), &query); err != nil {
   308  			e := gcserr.WrapHresult(err, gcserr.HrVmcomputeInvalidJSON)
   309  			return nil, errors.Wrapf(e, "The query could not be unmarshaled: '%s'", query)
   310  		}
   311  	}
   312  
   313  	if request.ContainerID == hcsv2.UVMContainerID {
   314  		return nil, errors.New("getPropertiesV2 is not supported against the UVM")
   315  	}
   316  
   317  	properties, err := b.hostState.GetProperties(ctx, request.ContainerID, query)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	propertyJSON := []byte("{}")
   323  	if properties != nil {
   324  		var err error
   325  		propertyJSON, err = json.Marshal(properties)
   326  		if err != nil {
   327  			return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%+v\"", properties)
   328  		}
   329  	}
   330  
   331  	return &prot.ContainerGetPropertiesResponse{
   332  		Properties: string(propertyJSON),
   333  	}, nil
   334  }
   335  
   336  func (b *Bridge) waitOnProcessV2(r *Request) (_ RequestResponse, err error) {
   337  	_, span := oc.StartSpan(r.Context, "opengcs::bridge::waitOnProcessV2")
   338  	defer span.End()
   339  	defer func() { oc.SetSpanStatus(span, err) }()
   340  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   341  
   342  	var request prot.ContainerWaitForProcess
   343  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   344  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   345  	}
   346  
   347  	span.AddAttributes(
   348  		trace.Int64Attribute("pid", int64(request.ProcessID)),
   349  		trace.Int64Attribute("timeout-ms", int64(request.TimeoutInMs)))
   350  
   351  	var exitCodeChan <-chan int
   352  	var doneChan chan<- bool
   353  
   354  	if request.ContainerID == hcsv2.UVMContainerID {
   355  		p, err := b.hostState.GetExternalProcess(int(request.ProcessID))
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  		exitCodeChan, doneChan = p.Wait()
   360  	} else {
   361  		c, err := b.hostState.GetCreatedContainer(request.ContainerID)
   362  		if err != nil {
   363  			return nil, err
   364  		}
   365  		p, err := c.GetProcess(request.ProcessID)
   366  		if err != nil {
   367  			return nil, err
   368  		}
   369  		exitCodeChan, doneChan = p.Wait()
   370  	}
   371  
   372  	// If we timed out or if we got the exit code. Acknowledge we no longer want to wait.
   373  	defer close(doneChan)
   374  
   375  	var tc <-chan time.Time
   376  	if request.TimeoutInMs != prot.InfiniteWaitTimeout {
   377  		t := time.NewTimer(time.Duration(request.TimeoutInMs) * time.Millisecond)
   378  		defer t.Stop()
   379  		tc = t.C
   380  	}
   381  	select {
   382  	case exitCode := <-exitCodeChan:
   383  		return &prot.ContainerWaitForProcessResponse{
   384  			ExitCode: uint32(exitCode),
   385  		}, nil
   386  	case <-tc:
   387  		return nil, gcserr.NewHresultError(gcserr.HvVmcomputeTimeout)
   388  	}
   389  }
   390  
   391  func (b *Bridge) resizeConsoleV2(r *Request) (_ RequestResponse, err error) {
   392  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::resizeConsoleV2")
   393  	defer span.End()
   394  	defer func() { oc.SetSpanStatus(span, err) }()
   395  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   396  
   397  	var request prot.ContainerResizeConsole
   398  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   399  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   400  	}
   401  
   402  	span.AddAttributes(
   403  		trace.Int64Attribute("pid", int64(request.ProcessID)),
   404  		trace.Int64Attribute("height", int64(request.Height)),
   405  		trace.Int64Attribute("width", int64(request.Width)))
   406  
   407  	c, err := b.hostState.GetCreatedContainer(request.ContainerID)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	p, err := c.GetProcess(request.ProcessID)
   413  	if err != nil {
   414  		return nil, err
   415  	}
   416  
   417  	err = p.ResizeConsole(ctx, request.Height, request.Width)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  
   422  	return &prot.MessageResponseBase{}, nil
   423  }
   424  
   425  func (b *Bridge) modifySettingsV2(r *Request) (_ RequestResponse, err error) {
   426  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::modifySettingsV2")
   427  	defer span.End()
   428  	defer func() { oc.SetSpanStatus(span, err) }()
   429  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   430  
   431  	request, err := prot.UnmarshalContainerModifySettings(r.Message)
   432  	if err != nil {
   433  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   434  	}
   435  
   436  	err = b.hostState.ModifySettings(ctx, request.ContainerID, request.Request.(*guestrequest.ModificationRequest))
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  
   441  	return &prot.MessageResponseBase{}, nil
   442  }
   443  
   444  func (b *Bridge) dumpStacksV2(r *Request) (_ RequestResponse, err error) {
   445  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::dumpStacksV2")
   446  	defer span.End()
   447  	defer func() { oc.SetSpanStatus(span, err) }()
   448  
   449  	stacks, err := b.hostState.GetStacks(ctx)
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  	return &prot.DumpStacksResponse{
   454  		GuestStacks: stacks,
   455  	}, nil
   456  }
   457  
   458  func (b *Bridge) deleteContainerStateV2(r *Request) (_ RequestResponse, err error) {
   459  	ctx, span := oc.StartSpan(r.Context, "opengcs::bridge::deleteContainerStateV2")
   460  	defer span.End()
   461  	defer func() { oc.SetSpanStatus(span, err) }()
   462  
   463  	span.AddAttributes(trace.StringAttribute("cid", r.ContainerID))
   464  
   465  	var request prot.MessageBase
   466  	if err := commonutils.UnmarshalJSONWithHresult(r.Message, &request); err != nil {
   467  		return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message)
   468  	}
   469  
   470  	c, err := b.hostState.GetCreatedContainer(request.ContainerID)
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  	// remove container state regardless of delete's success
   475  	defer b.hostState.RemoveContainer(request.ContainerID)
   476  
   477  	if err := c.Delete(ctx); err != nil {
   478  		return nil, err
   479  	}
   480  
   481  	return &prot.MessageResponseBase{}, nil
   482  }
   483  

View as plain text