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:
parent
d47bda91e9
commit
d3e0c163f8
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
// +build !windows_v2
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
|
25
cmd/containerd/builtins_windows_v2.go
Normal file
25
cmd/containerd/builtins_windows_v2.go
Normal 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"
|
||||
)
|
@ -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
|
||||
}
|
||||
|
102
runtime/v2/shim/shim_nix.go
Normal file
102
runtime/v2/shim/shim_nix.go
Normal 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
|
||||
}
|
96
runtime/v2/shim/shim_windows.go
Normal file
96
runtime/v2/shim/shim_windows.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
57
runtime/v2/shim/util_nix.go
Normal file
57
runtime/v2/shim/util_nix.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user