Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions cmd/containerd-shim-runhcs-v1/clone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"context"

"github.com/Microsoft/hcsshim/internal/clone"
"github.com/Microsoft/hcsshim/internal/uvm"
)

// saveAsTemplate saves the UVM and container inside it as a template and also stores the
// relevant information in the registry so that clones can be created from this template.
// Every cloned uvm gets its own NIC and we do not want to create clones of a template
// which still has a NIC attached to it. So remove the NICs attached to the template uvm
// before saving it.
// Similar to the NIC scenario we do not want to create clones from a template with an
// active GCS connection so close the GCS connection too.
func saveAsTemplate(ctx context.Context, templateTask *hcsTask) (err error) {
var utc *uvm.UVMTemplateConfig
var templateConfig *clone.TemplateConfig

if err = templateTask.host.RemoveAllNICs(ctx); err != nil {
return err
}

if err = templateTask.host.CloseGCSConnection(); err != nil {
return err
}

utc, err = templateTask.host.GenerateTemplateConfig()
if err != nil {
return err
}

templateConfig = &clone.TemplateConfig{
TemplateUVMID: utc.UVMID,
TemplateUVMResources: utc.Resources,
TemplateUVMCreateOpts: utc.CreateOpts,
TemplateContainerID: templateTask.id,
TemplateContainerSpec: *templateTask.taskSpec,
}

if err = clone.SaveTemplateConfig(ctx, templateConfig); err != nil {
return err
}

if err = templateTask.host.SaveAsTemplate(ctx); err != nil {
return err
}
return nil
}
67 changes: 67 additions & 0 deletions cmd/containerd-shim-runhcs-v1/exec_clone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"context"

"github.com/Microsoft/hcsshim/internal/cmd"
"github.com/Microsoft/hcsshim/internal/cow"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)

func newClonedExec(
ctx context.Context,
events publisher,
tid string,
host *uvm.UtilityVM,
c cow.Container,
id, bundle string,
isWCOW bool,
spec *specs.Process,
io cmd.UpstreamIO) *clonedExec {
log.G(ctx).WithFields(logrus.Fields{
"tid": tid,
"eid": id, // Init exec ID is always same as Task ID
"bundle": bundle,
}).Debug("newClonedExec")

he := &hcsExec{
events: events,
tid: tid,
host: host,
c: c,
id: id,
bundle: bundle,
isWCOW: isWCOW,
spec: spec,
io: io,
processDone: make(chan struct{}),
state: shimExecStateCreated,
exitStatus: 255, // By design for non-exited process status.
exited: make(chan struct{}),
}

ce := &clonedExec{
he,
}
go he.waitForContainerExit()
return ce
}

var _ = (shimExec)(&clonedExec{})

// clonedExec inherits from hcsExec. The only difference between these two is that
// on starting a clonedExec it doesn't attempt to start the container even if the
// exec is the init process. This is because in case of clonedExec the container is
// already running inside the pod.
type clonedExec struct {
*hcsExec
}

func (ce *clonedExec) Start(ctx context.Context) (err error) {
// A cloned exec should never initialize the container as it should
// already be running.
return ce.startInternal(ctx, false)
}
13 changes: 10 additions & 3 deletions cmd/containerd-shim-runhcs-v1/exec_hcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (he *hcsExec) Status() *task.StateResponse {
}
}

func (he *hcsExec) Start(ctx context.Context) (err error) {
func (he *hcsExec) startInternal(ctx context.Context, initializeContainer bool) (err error) {
he.sl.Lock()
defer he.sl.Unlock()
if he.state != shimExecStateCreated {
Expand All @@ -192,8 +192,7 @@ func (he *hcsExec) Start(ctx context.Context) (err error) {
he.exitFromCreatedL(ctx, 1)
}
}()
if he.id == he.tid {
// This is the init exec. We need to start the container itself
if initializeContainer {
err = he.c.Start(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -257,6 +256,12 @@ func (he *hcsExec) Start(ctx context.Context) (err error) {
return nil
}

func (he *hcsExec) Start(ctx context.Context) (err error) {
// If he.id == he.tid then this is the init exec.
// We need to initialize the container itself before starting this exec.
return he.startInternal(ctx, he.id == he.tid)
}

func (he *hcsExec) Kill(ctx context.Context, signal uint32) error {
he.sl.Lock()
defer he.sl.Unlock()
Expand Down Expand Up @@ -414,6 +419,8 @@ func (he *hcsExec) exitFromCreatedL(ctx context.Context, status int) {
//
// 6. Close `he.exited` channel to unblock any waiters who might have called
// `Create`/`Wait`/`Start` which is a valid pattern.
//
// 7. Finally, save the UVM and this container as a template if specified.
func (he *hcsExec) waitForExit() {
ctx, span := trace.StartSpan(context.Background(), "hcsExec::waitForExit")
defer span.End()
Expand Down
39 changes: 23 additions & 16 deletions cmd/containerd-shim-runhcs-v1/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,8 @@ func createPod(ctx context.Context, events publisher, req *task.CreateTaskReques
// isolated. Process isolated WCOW gets the namespace endpoints
// automatically.
if parent != nil {
nsid := ""
if s.Windows != nil && s.Windows.Network != nil {
nsid = s.Windows.Network.NetworkNamespace
}

if nsid != "" {
endpoints, err := hcsoci.GetNamespaceEndpoints(ctx, nsid)
if err != nil {
return nil, err
}
err = parent.AddNetNS(ctx, nsid)
if err != nil {
return nil, err
}
err = parent.AddEndpointsToNS(ctx, nsid, endpoints)
if s.Windows != nil && s.Windows.Network != nil && s.Windows.Network.NetworkNamespace != "" {
err = hcsoci.SetupNetworkNamespace(ctx, parent, s.Windows.Network.NetworkNamespace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -241,6 +228,16 @@ func (p *pod) ID() string {
return p.id
}

func (p *pod) GetCloneAnnotations(ctx context.Context, s *specs.Spec) (bool, string, error) {
isTemplate, templateID, err := oci.ParseCloneAnnotations(ctx, s)
if err != nil {
return false, "", err
} else if (isTemplate || templateID != "") && p.host == nil {
return false, "", fmt.Errorf("save as template and creating clones is only supported for hyper-v isolated containers")
}
return isTemplate, templateID, nil
}

func (p *pod) CreateTask(ctx context.Context, req *task.CreateTaskRequest, s *specs.Spec) (_ shimTask, err error) {
if req.ID == p.id {
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "task with id: '%s' already exists", req.ID)
Expand Down Expand Up @@ -283,7 +280,17 @@ func (p *pod) CreateTask(ctx context.Context, req *task.CreateTaskRequest, s *sp
sid)
}

st, err := newHcsTask(ctx, p.events, p.host, false, req, s)
_, templateID, err := p.GetCloneAnnotations(ctx, s)
if err != nil {
return nil, err
}

var st shimTask
if templateID != "" {
st, err = newClonedHcsTask(ctx, p.events, p.host, false, req, s, templateID)
} else {
st, err = newHcsTask(ctx, p.events, p.host, false, req, s)
}
if err != nil {
return nil, err
}
Expand Down
Loading