Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,11 @@ jobs:
- name: Install podman
run: sudo apt-get update -qq && sudo apt-get install -y podman

- name: Report container runtime versions
run: |
containerd --version || true
docker --version || true
podman --version || true

- name: Run e2e tests
run: go test -v -timeout 5m ./e2e/...
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
Comment thread
yejseo01 marked this conversation as resolved.
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
}
Comment thread
muchzill4 marked this conversation as resolved.
Outdated

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"),
Comment thread
yejseo01 marked this conversation as resolved.
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, we actually have to use the same minimum version as the vendored containerd, which is

Suggested change
- Go 1.26 or higher
- Go 1.26.3 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