Skip to content
Draft
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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ jobs:
- name: Run fast tests
run: go test -v -race ./internal/...

- name: Install containerd 2.3.2
env:
CONTAINERD_VERSION: 2.3.2
run: |
set -euxo pipefail

arch="$(dpkg --print-architecture)"
case "$arch" in
amd64|arm64) containerd_arch="$arch" ;;
*) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
esac

curl -fsSL \
"https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/containerd-${CONTAINERD_VERSION}-linux-${containerd_arch}.tar.gz" \
-o /tmp/containerd.tgz
sudo tar Cxzvf /usr/local /tmp/containerd.tgz

sudo mkdir -p /etc/systemd/system/containerd.service.d
sudo tee /etc/systemd/system/containerd.service.d/override.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/local/bin/containerd
EOF

sudo systemctl daemon-reload
sudo systemctl restart containerd
sudo systemctl restart docker

containerd --version | grep -Eq "v?${CONTAINERD_VERSION}"
ctr_version="$(sudo ctr version)"
echo "$ctr_version"
echo "$ctr_version" | awk '/^Server:/{server=1} server && /Version:/ {print; exit}' | grep -Eq "v?${CONTAINERD_VERSION}"

- name: Install podman
run: sudo apt-get update -qq && sudo apt-get install -y podman

Expand Down
12 changes: 11 additions & 1 deletion cmd/containerd-shim-remoteproc-v1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ package main

import (
"context"
"fmt"
"os"

"github.com/arm/remoteproc-runtime/internal/shim"
containerdshim "github.com/containerd/containerd/v2/pkg/shim"
)

func main() {
ctx := context.Background()
manager := shim.NewManager("io.containerd.remoteproc.v1")
containerdshim.Run(context.Background(), manager)
if handled, err := runStartCompat(ctx, manager); handled {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s", manager.Name(), err)
os.Exit(1)
}
return
}
containerdshim.RunShim(ctx, manager)
}
215 changes: 215 additions & 0 deletions cmd/containerd-shim-remoteproc-v1/start_compat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"os"

bootapi "github.com/containerd/containerd/api/runtime/bootstrap/v1"
"github.com/containerd/containerd/v2/pkg/namespaces"
"github.com/containerd/containerd/v2/pkg/protobuf/proto"
containerdshim "github.com/containerd/containerd/v2/pkg/shim"
"github.com/containerd/log"
)

type legacyBootstrapParams struct {
// Version is the version of shim parameters (expected 2 for shim v2).
Version int `json:"version"`
// Address is the address containerd should use to connect to shim.
Address string `json:"address"`
// Protocol is either TTRPC or GRPC.
Protocol string `json:"protocol"`
}

type shimFlags struct {
debug bool
version bool
info bool
id string
namespace string
socket string
debugSocket string
bundle string
address string
publishBinary string
action string
}

// runStartCompat handles the shim "start" action for both containerd 2.2.x and
// 2.3.x callers. containerd 2.2.x invokes shims using legacy CLI/env/stdin
// fields and requires a JSON bootstrap response, while containerd 2.3.x sends a
// bootstrap/v1 BootstrapParams protobuf on stdin and expects a BootstrapResult
// protobuf on stdout.
//
// The legacy start response handling is adapted from containerd's 2.2 shim
// runner:
// - Source: https://github.com/containerd/containerd/blob/main/pkg/shim/shim.go
// - Version: v2.2.5
// - Commit: e53c7c1516c3b2bff98eb76f1f4117477e6f4e66
// - License: Apache-2.0
func runStartCompat(ctx context.Context, manager containerdshim.Shim) (bool, error) {
flags, ok := parseShimFlags(os.Args[1:])
if !ok || flags.version || flags.info || flags.action != "start" {
return false, nil
}
if flags.namespace == "" {
return true, fmt.Errorf("shim namespace cannot be empty")
}

// Match containerd's shim runner limit: stdin should only contain bootstrap
// params or legacy runtime options, so cap it to avoid unbounded reads.
input, err := io.ReadAll(io.LimitReader(os.Stdin, 10<<20))
if err != nil {
return true, fmt.Errorf("failed to read stdin: %w", err)
}

parsed := parseBootstrapParams(input, flags)
params := parsed.params
if parsed.modern {
// Modern bootstrap params from containerd 2.3+ may include a socket dir.
// Since this compat path handles "start" before RunShim, persist it here too.
if dir := params.GetSocketDir(); dir != "" {
if err := writeCompatSocketDir(dir); err != nil {
return true, fmt.Errorf("failed to write socket-dir: %w", err)
}
}
}

ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", manager.Name()))
ctx = namespaces.WithNamespace(ctx, flags.namespace)
result, err := manager.Start(ctx, params)
if err != nil {
return true, err
}

buildResponse := buildLegacyResponse
if parsed.modern {
buildResponse = buildModernResponse
}
data, err := buildResponse(result)
if err != nil {
return true, err
}
_, err = os.Stdout.Write(data)
return true, err
}

func buildModernResponse(result *bootapi.BootstrapResult) ([]byte, error) {
data, err := proto.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to marshal bootstrap result: %w", err)
}
return data, nil
}

func buildLegacyResponse(result *bootapi.BootstrapResult) ([]byte, error) {
legacy := legacyBootstrapParams{
Version: int(result.GetVersion()),
Address: result.GetAddress(),
Protocol: result.GetProtocol(),
}
data, err := json.Marshal(&legacy)
if err != nil {
return nil, fmt.Errorf("failed to marshal bootstrap result to json: %w", err)
}
return data, nil
}

func parseShimFlags(args []string) (shimFlags, bool) {
var flags shimFlags
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(io.Discard)

fs.BoolVar(&flags.debug, "debug", false, "enable debug output in logs")
fs.BoolVar(&flags.version, "v", false, "")
fs.BoolVar(&flags.version, "version", false, "show the shim version and exit")
fs.BoolVar(&flags.info, "info", false, "get the option protobuf from stdin, print the shim info protobuf to stdout, and exit")
fs.StringVar(&flags.namespace, "namespace", "", "namespace that owns the shim")
fs.StringVar(&flags.id, "id", "", "id of the task")
fs.StringVar(&flags.socket, "socket", "", "socket path to serve")
fs.StringVar(&flags.debugSocket, "debug-socket", "", "debug socket path to serve")
fs.StringVar(&flags.bundle, "bundle", "", "path to the bundle if not workdir")
fs.StringVar(&flags.address, "address", "", "grpc address back to main containerd")
fs.StringVar(&flags.publishBinary, "publish-binary", "", "path to publish binary")

if err := fs.Parse(args); err != nil {
return shimFlags{}, false
}
flags.action = fs.Arg(0)
return flags, true
}

type parsedBootstrapParams struct {
params *bootapi.BootstrapParams
modern bool
}

func parseBootstrapParams(input []byte, flags shimFlags) parsedBootstrapParams {
var params bootapi.BootstrapParams
// Protobuf unmarshalling can succeed against legacy runtime options because
// unknown fields are ignored. Validate required bootstrap fields before
// treating stdin as a modern containerd 2.3+ BootstrapParams payload.
if len(input) > 0 && proto.Unmarshal(input, &params) == nil && validModernBootstrapParams(&params) && crossCheckBootstrapParams(&params, flags) {
return parsedBootstrapParams{params: &params, modern: true}
}

params = bootapi.BootstrapParams{
InstanceID: flags.id,
Namespace: flags.namespace,
LogLevel: bootapi.LogLevel_LOG_LEVEL_INFO,
ContainerdGrpcAddress: firstNonEmpty(flags.address, os.Getenv("GRPC_ADDRESS")),
ContainerdTtrpcAddress: os.Getenv("TTRPC_ADDRESS"),
ContainerdBinary: flags.publishBinary,
}
if flags.debug {
params.LogLevel = bootapi.LogLevel_LOG_LEVEL_DEBUG
}
return parsedBootstrapParams{params: &params}
}

func validModernBootstrapParams(params *bootapi.BootstrapParams) bool {
hasIdentity := params.GetInstanceID() != "" && params.GetNamespace() != ""
hasContainerdAddress := params.GetContainerdGrpcAddress() != "" || params.GetContainerdTtrpcAddress() != ""

return hasIdentity && hasContainerdAddress
}

func crossCheckBootstrapParams(params *bootapi.BootstrapParams, flags shimFlags) bool {
// containerd 2.3+ sends modern BootstrapParams on stdin but still includes
// legacy CLI fields for compatibility. When those fields are present, require
// them to agree with the protobuf payload so containerd 2.2 legacy runtime
// options are not mistaken for modern bootstrap params. If a future
// containerd drops the legacy flags, these checks are skipped.
if flags.id != "" && params.GetInstanceID() != flags.id {
return false
}
if flags.namespace != "" && params.GetNamespace() != flags.namespace {
return false
}
if flags.address != "" && params.GetContainerdGrpcAddress() != flags.address {
return false
}
return true
}

func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}

func writeCompatSocketDir(dir string) error {
const socketDirLink = "s"
if _, err := os.Lstat(socketDirLink); err == nil {
if err := os.Remove(socketDirLink); err != nil {
return fmt.Errorf("remove existing socket dir link: %w", err)
}
}
return os.Symlink(dir, socketDirLink)
}
6 changes: 3 additions & 3 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide covers how to build, test, and contribute to this project.

## Prerequisites

- Go 1.25 or higher
- Go 1.26 or higher
- [LimaVM](https://lima-vm.io) (for e2e testing)
- [Remoteproc Simulator](https://github.com/arm/remoteproc-simulator) (for e2e testing and manual testing without hardware)

Expand Down Expand Up @@ -57,7 +57,7 @@ go test -v -race ./internal/...
If you're developing on a non-Linux operating system, you can run the tests using Docker:

```bash
docker run --rm -v $(pwd):/app -w /app golang:1.25 go test -v ./internal/...
docker run --rm -v $(pwd):/app -w /app golang:1.26 go test -v ./internal/...
```

To improve feedback loop performance, you can use Docker volumes to cache Go modules and build artifacts:
Expand All @@ -73,7 +73,7 @@ docker run --rm \
-w /app \
-v go-mod-cache:/go/pkg/mod \
-v go-build-cache:/root/.cache/go-build \
golang:1.25 \
golang:1.26 \
go test -v ./internal/...
```

Expand Down
21 changes: 10 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/arm/remoteproc-runtime

go 1.26.1
go 1.26.3

require (
github.com/containerd/containerd/api v1.10.0
github.com/containerd/containerd/v2 v2.2.5
github.com/containerd/containerd/api v1.11.1
github.com/containerd/containerd/v2 v2.3.2
github.com/containerd/errdefs v1.0.0
github.com/containerd/log v0.1.0
github.com/containerd/plugin v1.1.0
Expand All @@ -17,19 +17,18 @@ require (
)

require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.14.1 // indirect
github.com/containerd/cgroups/v3 v3.1.2 // indirect
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect
github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect
github.com/containerd/cgroups/v3 v3.1.3 // indirect
github.com/containerd/console v1.0.5 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/continuity v0.5.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/go-runc v1.1.0 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
Expand All @@ -45,9 +44,9 @@ require (
golang.org/x/net v0.55.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading