From d3e0c163f8765bd253552221bf038d8089a39ff8 Mon Sep 17 00:00:00 2001 From: "Justin Terry (VM)" Date: Tue, 24 Jul 2018 15:19:06 -0700 Subject: [PATCH] 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) --- Makefile.windows | 7 ++ cmd/containerd/builtins_windows.go | 2 + cmd/containerd/builtins_windows_v2.go | 25 +++++++ runtime/v2/shim/shim.go | 69 +---------------- runtime/v2/shim/shim_nix.go | 102 ++++++++++++++++++++++++++ runtime/v2/shim/shim_windows.go | 96 ++++++++++++++++++++++++ runtime/v2/shim/util.go | 27 ------- runtime/v2/shim/util_nix.go | 57 ++++++++++++++ runtime/v2/shim/util_windows.go | 31 ++++++++ 9 files changed, 323 insertions(+), 93 deletions(-) create mode 100644 cmd/containerd/builtins_windows_v2.go create mode 100644 runtime/v2/shim/shim_nix.go create mode 100644 runtime/v2/shim/shim_windows.go create mode 100644 runtime/v2/shim/util_nix.go diff --git a/Makefile.windows b/Makefile.windows index 5dbb1f8a5..feefb9229 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -23,3 +23,10 @@ BINARY_SUFFIX=".exe" ifeq ($(GOARCH),amd64) TESTFLAGS_RACE= -race 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 diff --git a/cmd/containerd/builtins_windows.go b/cmd/containerd/builtins_windows.go index 6a0451edc..d81c62e87 100644 --- a/cmd/containerd/builtins_windows.go +++ b/cmd/containerd/builtins_windows.go @@ -1,3 +1,5 @@ +// +build !windows_v2 + /* Copyright The containerd Authors. diff --git a/cmd/containerd/builtins_windows_v2.go b/cmd/containerd/builtins_windows_v2.go new file mode 100644 index 000000000..f2dac227a --- /dev/null +++ b/cmd/containerd/builtins_windows_v2.go @@ -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" +) diff --git a/runtime/v2/shim/shim.go b/runtime/v2/shim/shim.go index 7592cf4ef..9187aa8a0 100644 --- a/runtime/v2/shim/shim.go +++ b/runtime/v2/shim/shim.go @@ -1,5 +1,3 @@ -// +build !windows - /* Copyright The containerd Authors. @@ -19,29 +17,22 @@ package shim import ( - "bytes" "context" "flag" "fmt" - "net" "os" - "os/exec" - "os/signal" "runtime" "runtime/debug" "strings" - "syscall" "time" "github.com/containerd/containerd/events" "github.com/containerd/containerd/namespaces" shimapi "github.com/containerd/containerd/runtime/v2/task" "github.com/containerd/ttrpc" - "github.com/containerd/typeurl" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) // Client for a shim server @@ -178,7 +169,7 @@ func NewShimClient(ctx context.Context, svc shimapi.TaskService, signals chan os // Serve the shim server func (s *Client) Serve() error { dump := make(chan os.Signal, 32) - signal.Notify(dump, syscall.SIGUSR1) + setupDumpStacks(dump) path, err := os.Getwd() 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 // this function does not block func serve(ctx context.Context, server *ttrpc.Server, path 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 errors.Errorf("%q: unix socket path too long (> 106)", path) - } - l, err = net.Listen("unix", "\x00"+path) - } + l, path, err := serveListener(path) if err != nil { return err } - logrus.WithField("socket", path).Debug("serving api on unix socket") + logrus.WithField("socket", path).Debug("serving api on abstract socket") go func() { defer l.Close() 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 } -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) { var ( buf []byte @@ -273,29 +236,3 @@ type remoteEventsPublisher struct { address 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 -} diff --git a/runtime/v2/shim/shim_nix.go b/runtime/v2/shim/shim_nix.go new file mode 100644 index 000000000..ba9b68be8 --- /dev/null +++ b/runtime/v2/shim/shim_nix.go @@ -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 +} diff --git a/runtime/v2/shim/shim_windows.go b/runtime/v2/shim/shim_windows.go new file mode 100644 index 000000000..ff49cb303 --- /dev/null +++ b/runtime/v2/shim/shim_windows.go @@ -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 +} diff --git a/runtime/v2/shim/util.go b/runtime/v2/shim/util.go index 29d5d41bf..1c3fb85c0 100644 --- a/runtime/v2/shim/util.go +++ b/runtime/v2/shim/util.go @@ -70,38 +70,11 @@ func BinaryName(runtime string) string { 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 func Connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) { 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 func WritePidFile(path string, pid int) error { path, err := filepath.Abs(path) diff --git a/runtime/v2/shim/util_nix.go b/runtime/v2/shim/util_nix.go new file mode 100644 index 000000000..63db21135 --- /dev/null +++ b/runtime/v2/shim/util_nix.go @@ -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 +} diff --git a/runtime/v2/shim/util_windows.go b/runtime/v2/shim/util_windows.go index b5a3ecb9d..f85e43dd3 100644 --- a/runtime/v2/shim/util_windows.go +++ b/runtime/v2/shim/util_windows.go @@ -17,7 +17,15 @@ package shim import ( + "context" + "fmt" + "net" "syscall" + "time" + + winio "github.com/Microsoft/go-winio" + "github.com/containerd/containerd/namespaces" + "github.com/pkg/errors" ) func getSysProcAttr() *syscall.SysProcAttr { @@ -28,3 +36,26 @@ func getSysProcAttr() *syscall.SysProcAttr { func SetScore(pid int) error { 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 +}