Adds runtime v2 support for Windows shim's

Implements the various requirements for the runtime v2 code to abstract
away the unix/linux code into the appropriate platform level
abstractions to use the runtime v2 on Windows as well.

Adds support in the Makefile.windows to actually build the runtime v2
code for Windows by setting a shell environment BUILD_WINDOWS_V2=1
before calling make. (Note this disables the compilation of the Windows
runtime v1)

Signed-off-by: Justin Terry (VM) <juterry@microsoft.com>
This commit is contained in:
Justin Terry (VM) 2018-07-24 15:19:06 -07:00
parent d47bda91e9
commit d3e0c163f8
9 changed files with 323 additions and 93 deletions

View File

@ -23,3 +23,10 @@ BINARY_SUFFIX=".exe"
ifeq ($(GOARCH),amd64) ifeq ($(GOARCH),amd64)
TESTFLAGS_RACE= -race TESTFLAGS_RACE= -race
endif endif
# add support for building the Windows v2 runtime
# based on the containerd-shim-runhcs-v1 shim rather
# than the existing runtime on hcsshim
ifeq (${BUILD_WINDOWS_V2},1)
BUILDTAGS += windows_v2
endif

View File

@ -1,3 +1,5 @@
// +build !windows_v2
/* /*
Copyright The containerd Authors. Copyright The containerd Authors.

View File

@ -0,0 +1,25 @@
// +build windows_v2
/*
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 main
import (
_ "github.com/containerd/containerd/diff/windows"
_ "github.com/containerd/containerd/runtime/v2"
_ "github.com/containerd/containerd/snapshots/windows"
)

View File

@ -1,5 +1,3 @@
// +build !windows
/* /*
Copyright The containerd Authors. Copyright The containerd Authors.
@ -19,29 +17,22 @@
package shim package shim
import ( import (
"bytes"
"context" "context"
"flag" "flag"
"fmt" "fmt"
"net"
"os" "os"
"os/exec"
"os/signal"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
"syscall"
"time" "time"
"github.com/containerd/containerd/events" "github.com/containerd/containerd/events"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
shimapi "github.com/containerd/containerd/runtime/v2/task" shimapi "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc" "github.com/containerd/ttrpc"
"github.com/containerd/typeurl"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
) )
// Client for a shim server // Client for a shim server
@ -178,7 +169,7 @@ func NewShimClient(ctx context.Context, svc shimapi.TaskService, signals chan os
// Serve the shim server // Serve the shim server
func (s *Client) Serve() error { func (s *Client) Serve() error {
dump := make(chan os.Signal, 32) dump := make(chan os.Signal, 32)
signal.Notify(dump, syscall.SIGUSR1) setupDumpStacks(dump)
path, err := os.Getwd() path, err := os.Getwd()
if err != nil { if err != nil {
@ -211,23 +202,11 @@ func (s *Client) Serve() error {
// serve serves the ttrpc API over a unix socket at the provided path // serve serves the ttrpc API over a unix socket at the provided path
// this function does not block // this function does not block
func serve(ctx context.Context, server *ttrpc.Server, path string) error { func serve(ctx context.Context, server *ttrpc.Server, path string) error {
var ( l, path, err := serveListener(path)
l net.Listener
err error
)
if path == "" {
l, err = net.FileListener(os.NewFile(3, "socket"))
path = "[inherited from parent]"
} else {
if len(path) > 106 {
return errors.Errorf("%q: unix socket path too long (> 106)", path)
}
l, err = net.Listen("unix", "\x00"+path)
}
if err != nil { if err != nil {
return err return err
} }
logrus.WithField("socket", path).Debug("serving api on unix socket") logrus.WithField("socket", path).Debug("serving api on abstract socket")
go func() { go func() {
defer l.Close() defer l.Close()
if err := server.Serve(ctx, l); err != nil && if err := server.Serve(ctx, l); err != nil &&
@ -238,22 +217,6 @@ func serve(ctx context.Context, server *ttrpc.Server, path string) error {
return nil return nil
} }
func handleSignals(logger *logrus.Entry, signals chan os.Signal) error {
logger.Info("starting signal loop")
for {
select {
case s := <-signals:
switch s {
case unix.SIGCHLD:
if err := Reap(); err != nil {
logger.WithError(err).Error("reap exit status")
}
case unix.SIGPIPE:
}
}
}
}
func dumpStacks(logger *logrus.Entry) { func dumpStacks(logger *logrus.Entry) {
var ( var (
buf []byte buf []byte
@ -273,29 +236,3 @@ type remoteEventsPublisher struct {
address string address string
containerdBinaryPath string containerdBinaryPath string
} }
func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error {
ns, _ := namespaces.Namespace(ctx)
encoded, err := typeurl.MarshalAny(event)
if err != nil {
return err
}
data, err := encoded.Marshal()
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, l.containerdBinaryPath, "--address", l.address, "publish", "--topic", topic, "--namespace", ns)
cmd.Stdin = bytes.NewReader(data)
c, err := Default.Start(cmd)
if err != nil {
return err
}
status, err := Default.Wait(cmd, c)
if err != nil {
return err
}
if status != 0 {
return errors.New("failed to publish event")
}
return nil
}

102
runtime/v2/shim/shim_nix.go Normal file
View File

@ -0,0 +1,102 @@
// +build !windows
/*
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 (
"bytes"
"context"
"net"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/containerd/containerd/events"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/typeurl"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func setupDumpStacks(dump chan<- os.Signal) {
signal.Notify(dump, syscall.SIGUSR1)
}
func serveListener(path string) (net.Listener, string, error) {
var (
l net.Listener
err error
)
if path == "" {
l, err = net.FileListener(os.NewFile(3, "socket"))
path = "[inherited from parent]"
} else {
if len(path) > 106 {
return nil, path, errors.Errorf("%q: unix socket path too long (> 106)", path)
}
l, err = net.Listen("unix", "\x00"+path)
}
if err != nil {
return nil, path, err
}
return l, path, nil
}
func handleSignals(logger *logrus.Entry, signals chan os.Signal) error {
logger.Info("starting signal loop")
for {
select {
case s := <-signals:
switch s {
case unix.SIGCHLD:
if err := Reap(); err != nil {
logger.WithError(err).Error("reap exit status")
}
case unix.SIGPIPE:
}
}
}
}
func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error {
ns, _ := namespaces.Namespace(ctx)
encoded, err := typeurl.MarshalAny(event)
if err != nil {
return err
}
data, err := encoded.Marshal()
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, l.containerdBinaryPath, "--address", l.address, "publish", "--topic", topic, "--namespace", ns)
cmd.Stdin = bytes.NewReader(data)
c, err := Default.Start(cmd)
if err != nil {
return err
}
status, err := Default.Wait(cmd, c)
if err != nil {
return err
}
if status != 0 {
return errors.New("failed to publish event")
}
return nil
}

View File

@ -0,0 +1,96 @@
// +build windows
/*
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"
"net"
"os"
winio "github.com/Microsoft/go-winio"
"github.com/containerd/containerd/events"
"github.com/containerd/ttrpc"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// setupSignals creates a new signal handler for all signals
func setupSignals() (chan os.Signal, error) {
signals := make(chan os.Signal, 32)
// TODO: JTERRY75: Make this based on events.
return signals, nil
}
func newServer() (*ttrpc.Server, error) {
return ttrpc.NewServer()
}
func subreaper() error {
return nil
}
func setupDumpStacks(dump chan<- os.Signal) {
// TODO: JTERRY75: Make this based on events. signal.Notify(dump, syscall.SIGUSR1)
}
// serve serves the ttrpc API over a unix socket at the provided path
// this function does not block
func serveListener(path string) (net.Listener, string, error) {
if path == "" {
return nil, path, errors.New("'socket' must be npipe path")
}
l, err := winio.ListenPipe(path, nil)
if err != nil {
return nil, path, err
}
return l, path, nil
}
func handleSignals(logger *logrus.Entry, signals chan os.Signal) error {
// TODO: JTERRY75: Make this based on events?
return nil
}
func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error {
/* TOOD: JTERRY75: Implement publish for windows
ns, _ := namespaces.Namespace(ctx)
encoded, err := typeurl.MarshalAny(event)
if err != nil {
return err
}
data, err := encoded.Marshal()
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, l.containerdBinaryPath, "--address", l.address, "publish", "--topic", topic, "--namespace", ns)
cmd.Stdin = bytes.NewReader(data)
c, err := Default.Start(cmd)
if err != nil {
return err
}
status, err := Default.Wait(cmd, c)
if err != nil {
return err
}
if status != 0 {
return errors.New("failed to publish event")
}
*/
return nil
}

View File

@ -70,38 +70,11 @@ func BinaryName(runtime string) string {
return fmt.Sprintf(shimBinaryFormat, parts[len(parts)-2], parts[len(parts)-1]) return fmt.Sprintf(shimBinaryFormat, parts[len(parts)-2], parts[len(parts)-1])
} }
// AbstractAddress returns an abstract socket address
func AbstractAddress(ctx context.Context, id string) (string, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return "", err
}
return filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock"), nil
}
// Connect to the provided address // Connect to the provided address
func Connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) { func Connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) {
return d(address, 100*time.Second) return d(address, 100*time.Second)
} }
// AnonDialer returns a dialer for an abstract socket
func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
address = strings.TrimPrefix(address, "unix://")
return net.DialTimeout("unix", "\x00"+address, timeout)
}
// NewSocket returns a new socket
func NewSocket(address string) (*net.UnixListener, error) {
if len(address) > 106 {
return nil, errors.Errorf("%q: unix socket path too long (> 106)", address)
}
l, err := net.Listen("unix", "\x00"+address)
if err != nil {
return nil, errors.Wrapf(err, "failed to listen to abstract unix socket %q", address)
}
return l.(*net.UnixListener), nil
}
// WritePidFile writes a pid file atomically // WritePidFile writes a pid file atomically
func WritePidFile(path string, pid int) error { func WritePidFile(path string, pid int) error {
path, err := filepath.Abs(path) path, err := filepath.Abs(path)

View File

@ -0,0 +1,57 @@
// +build !windows
/*
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"
"net"
"path/filepath"
"strings"
"time"
"github.com/containerd/containerd/namespaces"
"github.com/pkg/errors"
)
// AbstractAddress returns an abstract socket address
func AbstractAddress(ctx context.Context, id string) (string, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return "", err
}
return filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock"), nil
}
// AnonDialer returns a dialer for an abstract socket
func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
address = strings.TrimPrefix(address, "unix://")
return net.DialTimeout("unix", "\x00"+address, timeout)
}
// NewSocket returns a new socket
func NewSocket(address string) (*net.UnixListener, error) {
if len(address) > 106 {
return nil, errors.Errorf("%q: unix socket path too long (> 106)", address)
}
l, err := net.Listen("unix", "\x00"+address)
if err != nil {
return nil, errors.Wrapf(err, "failed to listen to abstract unix socket %q", address)
}
return l.(*net.UnixListener), nil
}

View File

@ -17,7 +17,15 @@
package shim package shim
import ( import (
"context"
"fmt"
"net"
"syscall" "syscall"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/containerd/containerd/namespaces"
"github.com/pkg/errors"
) )
func getSysProcAttr() *syscall.SysProcAttr { func getSysProcAttr() *syscall.SysProcAttr {
@ -28,3 +36,26 @@ func getSysProcAttr() *syscall.SysProcAttr {
func SetScore(pid int) error { func SetScore(pid int) error {
return nil return nil
} }
// AbstractAddress returns an abstract npipe address
func AbstractAddress(ctx context.Context, id string) (string, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return "", err
}
return fmt.Sprintf("\\\\.\\pipe\\containerd-shim-%s-%s-pipe", ns, id), nil
}
// AnonDialer returns a dialer for an abstract npipe
func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
return winio.DialPipe(address, &timeout)
}
// NewSocket returns a new npipe listener
func NewSocket(address string) (net.Listener, error) {
l, err := winio.ListenPipe(address, nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to listen to abstract npipe %s", address)
}
return l, nil
}