NOISSUE - Update protobuf generated files and improve algorithm handling in runner service (#612)
CI / lint (push) Has been cancelled
CI / checkproto (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled

* Update protobuf generated files and improve algorithm handling in runner service

- Bump protoc version from v7.35.0 to v7.35.1 in generated protobuf files.
- Refactor RunRequest message in runner.proto to use string paths for algorithm and requirements instead of byte arrays.
- Update runner service to handle algorithm and requirements paths, removing the need for temporary file creation.
- Enhance error handling for missing algorithm paths in the runner service.
- Modify tests to align with the new RunRequest structure and ensure proper file handling.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: update protoc version to 35.1 in CI workflow

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: update systemd service configurations and dependencies for improved service management

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: adjust AlgoWorkingDir handling in tests for Docker algorithm

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
This commit is contained in:
Sammy Kerata Oina
2026-07-28 14:54:43 +03:00
committed by GitHub
parent 412472943d
commit 22c14cb438
32 changed files with 279 additions and 213 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Set up protoc - name: Set up protoc
run: | run: |
PROTOC_VERSION=35.0 PROTOC_VERSION=35.1
PROTOC_GEN_VERSION=v1.36.11 PROTOC_GEN_VERSION=v1.36.11
PROTOC_GRPC_VERSION=v1.6.0 PROTOC_GRPC_VERSION=v1.6.0
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: agent/agent.proto // source: agent/agent.proto
package agent package agent
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: agent/agent.proto // source: agent/agent.proto
package agent package agent
+6 -3
View File
@@ -18,11 +18,14 @@ const (
AlgoTypeKey = "algo_type" AlgoTypeKey = "algo_type"
AlgoArgsKey = "algo_args" AlgoArgsKey = "algo_args"
ResultsDir = "results" ResultsDir = "results"
DatasetsDir = "datasets" DatasetsDir = "datasets"
AlgoWorkingDir = "/cocos"
) )
// AlgoWorkingDir is the base directory used by algorithm runners (e.g. docker)
// to create datasets/results mounts. It is a variable so tests can override it.
var AlgoWorkingDir = "/cocos"
func AlgorithmTypeToContext(ctx context.Context, algoType string) context.Context { func AlgorithmTypeToContext(ctx context.Context, algoType string) context.Context {
return metadata.AppendToOutgoingContext(ctx, AlgoTypeKey, algoType) return metadata.AppendToOutgoingContext(ctx, AlgoTypeKey, algoType)
} }
+10
View File
@@ -90,6 +90,16 @@ func (d *docker) Run() error {
return fmt.Errorf("could not find image ID") return fmt.Errorf("could not find image ID")
} }
datasetsDir := path.Join(algorithm.AlgoWorkingDir, algorithm.DatasetsDir)
resultsDir := path.Join(algorithm.AlgoWorkingDir, algorithm.ResultsDir)
if err := os.MkdirAll(datasetsDir, 0o755); err != nil {
return fmt.Errorf("could not create datasets directory %s: %v", datasetsDir, err)
}
if err := os.MkdirAll(resultsDir, 0o755); err != nil {
return fmt.Errorf("could not create results directory %s: %v", resultsDir, err)
}
// Create and start the container. // Create and start the container.
respContainer, err := cli.ContainerCreate(ctx, &container.Config{ respContainer, err := cli.ContainerCreate(ctx, &container.Config{
Image: dockerImageName, Image: dockerImageName,
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: agent/cvms/cvms.proto // source: agent/cvms/cvms.proto
package cvms package cvms
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: agent/cvms/cvms.proto // source: agent/cvms/cvms.proto
package cvms package cvms
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: agent/events/events.proto // source: agent/events/events.proto
package events package events
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: agent/log/log.proto // source: agent/log/log.proto
package log package log
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: agent/log/log.proto // source: agent/log/log.proto
package log package log
+20 -20
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: agent/runner/runner.proto // source: agent/runner/runner.proto
package runner package runner
@@ -26,15 +26,15 @@ const (
) )
type RunRequest struct { type RunRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ComputationId string `protobuf:"bytes,1,opt,name=computation_id,json=computationId,proto3" json:"computation_id,omitempty"` ComputationId string `protobuf:"bytes,1,opt,name=computation_id,json=computationId,proto3" json:"computation_id,omitempty"`
AlgoType string `protobuf:"bytes,2,opt,name=algo_type,json=algoType,proto3" json:"algo_type,omitempty"` // "binary", "python", "wasm", "docker" AlgoType string `protobuf:"bytes,2,opt,name=algo_type,json=algoType,proto3" json:"algo_type,omitempty"` // "binary", "python", "wasm", "docker"
Algorithm []byte `protobuf:"bytes,3,opt,name=algorithm,proto3" json:"algorithm,omitempty"` // The algorithm binary/script content AlgorithmPath string `protobuf:"bytes,3,opt,name=algorithm_path,json=algorithmPath,proto3" json:"algorithm_path,omitempty"` // Path to the staged algorithm artifact
Requirements []byte `protobuf:"bytes,4,opt,name=requirements,proto3" json:"requirements,omitempty"` // Python requirements.txt content RequirementsPath string `protobuf:"bytes,4,opt,name=requirements_path,json=requirementsPath,proto3" json:"requirements_path,omitempty"` // Optional path to staged Python requirements.txt
Args []string `protobuf:"bytes,5,rep,name=args,proto3" json:"args,omitempty"` Args []string `protobuf:"bytes,5,rep,name=args,proto3" json:"args,omitempty"`
Datasets []*Dataset `protobuf:"bytes,6,rep,name=datasets,proto3" json:"datasets,omitempty"` Datasets []*Dataset `protobuf:"bytes,6,rep,name=datasets,proto3" json:"datasets,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *RunRequest) Reset() { func (x *RunRequest) Reset() {
@@ -81,18 +81,18 @@ func (x *RunRequest) GetAlgoType() string {
return "" return ""
} }
func (x *RunRequest) GetAlgorithm() []byte { func (x *RunRequest) GetAlgorithmPath() string {
if x != nil { if x != nil {
return x.Algorithm return x.AlgorithmPath
} }
return nil return ""
} }
func (x *RunRequest) GetRequirements() []byte { func (x *RunRequest) GetRequirementsPath() string {
if x != nil { if x != nil {
return x.Requirements return x.RequirementsPath
} }
return nil return ""
} }
func (x *RunRequest) GetArgs() []string { func (x *RunRequest) GetArgs() []string {
@@ -261,13 +261,13 @@ var File_agent_runner_runner_proto protoreflect.FileDescriptor
const file_agent_runner_runner_proto_rawDesc = "" + const file_agent_runner_runner_proto_rawDesc = "" +
"\n" + "\n" +
"\x19agent/runner/runner.proto\x12\x06runner\x1a\x1bgoogle/protobuf/empty.proto\"\xd3\x01\n" + "\x19agent/runner/runner.proto\x12\x06runner\x1a\x1bgoogle/protobuf/empty.proto\"\xe5\x01\n" +
"\n" + "\n" +
"RunRequest\x12%\n" + "RunRequest\x12%\n" +
"\x0ecomputation_id\x18\x01 \x01(\tR\rcomputationId\x12\x1b\n" + "\x0ecomputation_id\x18\x01 \x01(\tR\rcomputationId\x12\x1b\n" +
"\talgo_type\x18\x02 \x01(\tR\balgoType\x12\x1c\n" + "\talgo_type\x18\x02 \x01(\tR\balgoType\x12%\n" +
"\talgorithm\x18\x03 \x01(\fR\talgorithm\x12\"\n" + "\x0ealgorithm_path\x18\x03 \x01(\tR\ralgorithmPath\x12+\n" +
"\frequirements\x18\x04 \x01(\fR\frequirements\x12\x12\n" + "\x11requirements_path\x18\x04 \x01(\tR\x10requirementsPath\x12\x12\n" +
"\x04args\x18\x05 \x03(\tR\x04args\x12+\n" + "\x04args\x18\x05 \x03(\tR\x04args\x12+\n" +
"\bdatasets\x18\x06 \x03(\v2\x0f.runner.DatasetR\bdatasets\"9\n" + "\bdatasets\x18\x06 \x03(\v2\x0f.runner.DatasetR\bdatasets\"9\n" +
"\aDataset\x12\x1a\n" + "\aDataset\x12\x1a\n" +
+2 -2
View File
@@ -17,8 +17,8 @@ service ComputationRunner {
message RunRequest { message RunRequest {
string computation_id = 1; string computation_id = 1;
string algo_type = 2; // "binary", "python", "wasm", "docker" string algo_type = 2; // "binary", "python", "wasm", "docker"
bytes algorithm = 3; // The algorithm binary/script content string algorithm_path = 3; // Path to the staged algorithm artifact
bytes requirements = 4; // Python requirements.txt content string requirements_path = 4; // Optional path to staged Python requirements.txt
repeated string args = 5; repeated string args = 5;
repeated Dataset datasets = 6; repeated Dataset datasets = 6;
} }
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: agent/runner/runner.proto // source: agent/runner/runner.proto
package runner package runner
+6 -53
View File
@@ -6,8 +6,6 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"path/filepath"
"sync" "sync"
"github.com/ultravioletrs/cocos/agent/algorithm" "github.com/ultravioletrs/cocos/agent/algorithm"
@@ -20,10 +18,6 @@ import (
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
) )
const (
algoFilePermission = 0o700
)
var _ pb.ComputationRunnerServer = (*RunnerService)(nil) var _ pb.ComputationRunnerServer = (*RunnerService)(nil)
type RunnerService struct { type RunnerService struct {
@@ -58,65 +52,24 @@ func (s *RunnerService) Run(ctx context.Context, req *pb.RunRequest) (*pb.RunRes
s.mu.Unlock() s.mu.Unlock()
}() }()
currentDir, err := os.Getwd() if req.AlgorithmPath == "" {
if err != nil { return nil, fmt.Errorf("algorithm path is required")
return nil, fmt.Errorf("error getting current directory: %v", err)
} }
// Write Algo File
algoPath := filepath.Join(currentDir, "algo")
f, err := os.Create(algoPath)
if err != nil {
return nil, fmt.Errorf("error creating algorithm file: %v", err)
}
if _, err := f.Write(req.Algorithm); err != nil {
return nil, fmt.Errorf("error writing algorithm to file: %v", err)
}
if err := os.Chmod(algoPath, algoFilePermission); err != nil {
return nil, fmt.Errorf("error changing file permissions: %v", err)
}
if err := f.Close(); err != nil {
return nil, fmt.Errorf("error closing file: %v", err)
}
defer func() {
if err := os.Remove(algoPath); err != nil {
s.logger.Warn("error removing algorithm file", "error", err)
}
}()
var algo algorithm.Algorithm var algo algorithm.Algorithm
switch req.AlgoType { switch req.AlgoType {
case string(algorithm.AlgoTypeBin): case string(algorithm.AlgoTypeBin):
algo = binary.NewAlgorithm(s.logger, s.eventSvc, algoPath, req.Args, req.ComputationId) algo = binary.NewAlgorithm(s.logger, s.eventSvc, req.AlgorithmPath, req.Args, req.ComputationId)
case string(algorithm.AlgoTypePython): case string(algorithm.AlgoTypePython):
var requirementsFile string
if len(req.Requirements) > 0 {
fr, err := os.CreateTemp("", "requirements.txt")
if err != nil {
return nil, fmt.Errorf("error creating requirments file: %v", err)
}
defer func() {
if err := os.Remove(fr.Name()); err != nil {
s.logger.Warn("error removing requirements file", "error", err)
}
}()
if _, err := fr.Write(req.Requirements); err != nil {
return nil, fmt.Errorf("error writing requirements to file: %v", err)
}
if err := fr.Close(); err != nil {
return nil, fmt.Errorf("error closing file: %v", err)
}
requirementsFile = fr.Name()
}
// Assuming default python runtime if not specified in request (proto doesn't have runtime field yet) // Assuming default python runtime if not specified in request (proto doesn't have runtime field yet)
// We can add it or assume. // We can add it or assume.
runtime := python.PyRuntime runtime := python.PyRuntime
algo = python.NewAlgorithm(s.logger, s.eventSvc, runtime, requirementsFile, algoPath, req.Args, req.ComputationId) algo = python.NewAlgorithm(s.logger, s.eventSvc, runtime, req.RequirementsPath, req.AlgorithmPath, req.Args, req.ComputationId)
case string(algorithm.AlgoTypeWasm): case string(algorithm.AlgoTypeWasm):
algo = wasm.NewAlgorithm(s.logger, s.eventSvc, req.Args, algoPath, req.ComputationId) algo = wasm.NewAlgorithm(s.logger, s.eventSvc, req.Args, req.AlgorithmPath, req.ComputationId)
case string(algorithm.AlgoTypeDocker): case string(algorithm.AlgoTypeDocker):
algo = docker.NewAlgorithm(s.logger, s.eventSvc, algoPath, req.ComputationId) algo = docker.NewAlgorithm(s.logger, s.eventSvc, req.AlgorithmPath, req.ComputationId)
default: default:
return nil, fmt.Errorf("unsupported algorithm type: %s", req.AlgoType) return nil, fmt.Errorf("unsupported algorithm type: %s", req.AlgoType)
} }
+53 -76
View File
@@ -8,11 +8,13 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"path/filepath"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent/algorithm"
pb "github.com/ultravioletrs/cocos/agent/runner" pb "github.com/ultravioletrs/cocos/agent/runner"
) )
@@ -30,6 +32,15 @@ func (m *MockEventService) SendEvent(cmpID, event, status string, details json.R
}) })
} }
func writeRunnerTestFile(t *testing.T, dir, name string, data []byte, mode os.FileMode) string {
t.Helper()
path := filepath.Join(dir, name)
require.NoError(t, os.WriteFile(path, data, mode))
return path
}
// TestNewRunnerService tests the creation of a new runner service. // TestNewRunnerService tests the creation of a new runner service.
func TestNewRunnerService(t *testing.T) { func TestNewRunnerService(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
@@ -48,6 +59,7 @@ func TestRunWithBinaryAlgorithm(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
require.NoError(t, os.Chdir(tmpDir)) require.NoError(t, os.Chdir(tmpDir))
defer func() { require.NoError(t, os.Chdir(origDir)) }() defer func() { require.NoError(t, os.Chdir(origDir)) }()
algoPath := writeRunnerTestFile(t, tmpDir, "algo", []byte("#!/bin/bash\necho 'test'"), 0o700)
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
@@ -56,7 +68,7 @@ func TestRunWithBinaryAlgorithm(t *testing.T) {
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-1", ComputationId: "test-1",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("#!/bin/bash\necho 'test'"), AlgorithmPath: algoPath,
Args: []string{"arg1", "arg2"}, Args: []string{"arg1", "arg2"},
} }
@@ -72,13 +84,16 @@ func TestRunWithPythonAlgorithm(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo.py", []byte("print('hello')"), 0o600)
requirementsPath := writeRunnerTestFile(t, tmpDir, "requirements.txt", []byte("numpy==2.2.0"), 0o600)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-python", ComputationId: "test-python",
AlgoType: "python", AlgoType: "python",
Algorithm: []byte("print('hello')"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
Requirements: []byte("numpy==2.2.0"), RequirementsPath: requirementsPath,
} }
resp, err := rs.Run(context.Background(), req) resp, err := rs.Run(context.Background(), req)
@@ -86,9 +101,6 @@ func TestRunWithPythonAlgorithm(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Empty(t, resp.Error) assert.Empty(t, resp.Error)
assert.Equal(t, "test-python", resp.ComputationId) assert.Equal(t, "test-python", resp.ComputationId)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunWithPythonAlgorithmNoRequirements tests running Python without requirements. // TestRunWithPythonAlgorithmNoRequirements tests running Python without requirements.
@@ -96,11 +108,13 @@ func TestRunWithPythonAlgorithmNoRequirements(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo.py", []byte("print('hello')"), 0o600)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-python-noreq", ComputationId: "test-python-noreq",
AlgoType: "python", AlgoType: "python",
Algorithm: []byte("print('hello')"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -109,9 +123,6 @@ func TestRunWithPythonAlgorithmNoRequirements(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Empty(t, resp.Error) assert.Empty(t, resp.Error)
assert.Equal(t, "test-python-noreq", resp.ComputationId) assert.Equal(t, "test-python-noreq", resp.ComputationId)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunWithWasmAlgorithm tests running a WASM algorithm. // TestRunWithWasmAlgorithm tests running a WASM algorithm.
@@ -119,11 +130,13 @@ func TestRunWithWasmAlgorithm(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo.wasm", []byte{0x00, 0x61, 0x73, 0x6d}, 0o600)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-wasm", ComputationId: "test-wasm",
AlgoType: "wasm", AlgoType: "wasm",
Algorithm: []byte{0x00, 0x61, 0x73, 0x6d}, AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -135,9 +148,6 @@ func TestRunWithWasmAlgorithm(t *testing.T) {
t.Skip("wasmedge not found, skipping test") t.Skip("wasmedge not found, skipping test")
} }
assert.Equal(t, "test-wasm", resp.ComputationId) assert.Equal(t, "test-wasm", resp.ComputationId)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunWithDockerAlgorithm tests running a Docker algorithm. // TestRunWithDockerAlgorithm tests running a Docker algorithm.
@@ -145,11 +155,17 @@ func TestRunWithDockerAlgorithm(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "Dockerfile", []byte("FROM ubuntu:latest\nRUN echo 'test'"), 0o600)
origWorkingDir := algorithm.AlgoWorkingDir
algorithm.AlgoWorkingDir = tmpDir
t.Cleanup(func() { algorithm.AlgoWorkingDir = origWorkingDir })
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-docker", ComputationId: "test-docker",
AlgoType: "docker", AlgoType: "docker",
Algorithm: []byte("FROM ubuntu:latest\nRUN echo 'test'"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -161,9 +177,6 @@ func TestRunWithDockerAlgorithm(t *testing.T) {
t.Skip("Docker issue, skipping test") t.Skip("Docker issue, skipping test")
} }
assert.Equal(t, "test-docker", resp.ComputationId) assert.Equal(t, "test-docker", resp.ComputationId)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunWithUnsupportedAlgorithmType tests running with unsupported algorithm type. // TestRunWithUnsupportedAlgorithmType tests running with unsupported algorithm type.
@@ -175,7 +188,7 @@ func TestRunWithUnsupportedAlgorithmType(t *testing.T) {
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-unsupported", ComputationId: "test-unsupported",
AlgoType: "unsupported", AlgoType: "unsupported",
Algorithm: []byte("test"), AlgorithmPath: "/tmp/test",
Args: []string{}, Args: []string{},
} }
@@ -189,12 +202,14 @@ func TestRunAlreadyRunning(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo", []byte("#!/bin/bash\nsleep 30"), 0o700)
// Use a long-running bash script // Use a long-running bash script
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-running", ComputationId: "test-running",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("#!/bin/bash\nsleep 30"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -211,9 +226,6 @@ func TestRunAlreadyRunning(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Equal(t, "computation already running", resp.Error) assert.Equal(t, "computation already running", resp.Error)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestStopWhenRunning tests stopping a running computation. // TestStopWhenRunning tests stopping a running computation.
@@ -221,11 +233,13 @@ func TestStopWhenRunning(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo", []byte("#!/bin/bash\nsleep 10"), 0o700)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-stop", ComputationId: "test-stop",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("#!/bin/bash\nsleep 10"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -243,9 +257,6 @@ func TestStopWhenRunning(t *testing.T) {
stopResp, err := rs.Stop(context.Background(), stopReq) stopResp, err := rs.Stop(context.Background(), stopReq)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, stopResp) require.NotNil(t, stopResp)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunErrors tests error paths in Run. // TestRunErrors tests error paths in Run.
@@ -255,59 +266,27 @@ func TestRunErrors(t *testing.T) {
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
t.Run("create algo file failure", func(t *testing.T) { t.Run("create algo file failure", func(t *testing.T) {
// Create a directory named "algo" to make os.Create("algo") fail var err error
err := os.Mkdir("algo", 0o755)
require.NoError(t, err)
defer os.RemoveAll("algo")
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-err", ComputationId: "test-err",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("test"), AlgorithmPath: "",
} }
_, err = rs.Run(context.Background(), req) _, err = rs.Run(context.Background(), req)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "error creating algorithm file") assert.Contains(t, err.Error(), "algorithm path is required")
})
t.Run("getwd failure", func(t *testing.T) {
origDir, _ := os.Getwd()
tmpDir := t.TempDir()
err := os.Chdir(tmpDir)
require.NoError(t, err)
// Remove the current working directory to trigger Getwd failure
err = os.RemoveAll(tmpDir)
require.NoError(t, err)
req := &pb.RunRequest{
ComputationId: "test-err-getwd",
AlgoType: "bin",
Algorithm: []byte("test"),
}
_, err = rs.Run(context.Background(), req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "error getting current directory")
// Restore working directory
_ = os.Chdir(origDir)
}) })
t.Run("requirements file creation failure", func(t *testing.T) { t.Run("requirements file creation failure", func(t *testing.T) {
// This one is harder because it uses os.CreateTemp("", "requirements.txt") // Requirements are now staged by the agent, so the runner no longer creates temp files.
// We can't easily make this fail without reaching into the system's temp dir.
// Skipping for now as it's a very unlikely edge case.
}) })
t.Run("chmod failure", func(t *testing.T) { t.Run("chmod failure", func(t *testing.T) {
// We can't easily mock os.Chmod, but we can try to make the file unmodifiable // Permission management is now the agent's responsibility during staging.
// On Linux, we can set the immutable attribute, but that requires root.
// Alternatively, we can try to use a directory with permissions that prevent chmod?
// No, chmod usually works if you own the file.
}) })
t.Run("write algorithm failure", func(t *testing.T) { t.Run("write algorithm failure", func(t *testing.T) {
// This is also hard without mocking os.File.Write or reaching internal limits. // Write failures are now handled by the agent before invoking the runner.
}) })
} }
@@ -316,11 +295,13 @@ func TestConcurrentRun(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo", []byte("#!/bin/bash\nsleep 15"), 0o700)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-concurrent", ComputationId: "test-concurrent",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("#!/bin/bash\nsleep 15"), AlgorithmPath: algoPath,
Args: []string{}, Args: []string{},
} }
@@ -336,9 +317,6 @@ func TestConcurrentRun(t *testing.T) {
resp2, err := rs.Run(context.Background(), req) resp2, err := rs.Run(context.Background(), req)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "computation already running", resp2.Error) assert.Equal(t, "computation already running", resp2.Error)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
// TestRunWithMultipleArgs tests running with multiple arguments. // TestRunWithMultipleArgs tests running with multiple arguments.
@@ -346,11 +324,13 @@ func TestRunWithMultipleArgs(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventSvc := &MockEventService{} eventSvc := &MockEventService{}
rs := New(logger, eventSvc) rs := New(logger, eventSvc)
tmpDir := t.TempDir()
algoPath := writeRunnerTestFile(t, tmpDir, "algo", []byte("#!/bin/bash\necho $@"), 0o700)
req := &pb.RunRequest{ req := &pb.RunRequest{
ComputationId: "test-multi-args", ComputationId: "test-multi-args",
AlgoType: "bin", AlgoType: "bin",
Algorithm: []byte("#!/bin/bash\necho $@"), AlgorithmPath: algoPath,
Args: []string{"arg1", "arg2", "arg3", "arg4"}, Args: []string{"arg1", "arg2", "arg3", "arg4"},
} }
@@ -359,9 +339,6 @@ func TestRunWithMultipleArgs(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Empty(t, resp.Error) assert.Empty(t, resp.Error)
assert.Equal(t, "test-multi-args", resp.ComputationId) assert.Equal(t, "test-multi-args", resp.ComputationId)
t.Cleanup(func() {
_ = os.Remove("algo")
})
} }
func TestStopFailure(t *testing.T) { func TestStopFailure(t *testing.T) {
+77 -32
View File
@@ -82,7 +82,9 @@ const (
) )
const ( const (
algoFilePermission = 0o700 algoFilePermission = 0o700
algoFileName = "algo"
requirementsFileName = "requirements.txt"
) )
var ( var (
@@ -113,6 +115,30 @@ func ensureDir(path string, mode os.FileMode) error {
return nil return nil
} }
func writeFile(path string, data []byte, mode os.FileMode) error {
if err := os.WriteFile(path, data, mode); err != nil {
return fmt.Errorf("writing file %q: %w", path, err)
}
return nil
}
func stageRequirementsFile(workDir string, requirements []byte) (string, error) {
requirementsPath := filepath.Join(workDir, requirementsFileName)
if len(requirements) == 0 {
if err := os.Remove(requirementsPath); err != nil && !os.IsNotExist(err) {
return "", fmt.Errorf("removing stale requirements file: %w", err)
}
return "", nil
}
if err := writeFile(requirementsPath, requirements, 0o600); err != nil {
return "", err
}
return requirementsPath, nil
}
var ( var (
// ErrMalformedEntity indicates malformed entity specification (e.g. // ErrMalformedEntity indicates malformed entity specification (e.g.
// invalid username or password). // invalid username or password).
@@ -364,10 +390,14 @@ func (as *agentService) StopComputation(ctx context.Context) error {
return fmt.Errorf("error removing results directory: %v", err) return fmt.Errorf("error removing results directory: %v", err)
} }
if err := os.Remove("algo"); err != nil && !os.IsNotExist(err) { if err := os.Remove(algoFileName); err != nil && !os.IsNotExist(err) {
as.logger.Warn("error removing algorithm file", "error", err) as.logger.Warn("error removing algorithm file", "error", err)
} }
if err := os.Remove(requirementsFileName); err != nil && !os.IsNotExist(err) {
as.logger.Warn("error removing requirements file", "error", err)
}
as.sm.Reset(Idle) as.sm.Reset(Idle)
as.computation = Computation{} as.computation = Computation{}
@@ -448,9 +478,9 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
} }
// Write algorithm to file // Write algorithm to file
currentDir, err := os.Getwd() currentDir, getwdErr := os.Getwd()
if err != nil { if getwdErr != nil {
as.runError = fmt.Errorf("error getting current directory: %w", err) as.runError = fmt.Errorf("error getting current directory: %w", getwdErr)
as.logger.Error(as.runError.Error()) as.logger.Error(as.runError.Error())
as.sm.SendEvent(RunFailed) as.sm.SendEvent(RunFailed)
return return
@@ -504,7 +534,14 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
} }
as.algoReceived = true as.algoReceived = true
as.algoRequirements = res.Requirements // Store requirements for installation as.algoRequirements = res.Requirements
if _, err := stageRequirementsFile(currentDir, as.algoRequirements); err != nil {
as.runError = fmt.Errorf("error staging requirements file: %w", err)
as.logger.Error(as.runError.Error())
as.sm.SendEvent(RunFailed)
return
}
// The initramfs may have already provisioned /cocos/datasets. // The initramfs may have already provisioned /cocos/datasets.
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil { if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
@@ -1053,21 +1090,9 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
return fmt.Errorf("error getting current directory: %v", err) return fmt.Errorf("error getting current directory: %v", err)
} }
f, err := os.Create(filepath.Join(currentDir, "algo")) algoPath := filepath.Join(currentDir, algoFileName)
if err != nil { if err := writeFile(algoPath, algoData, algoFilePermission); err != nil {
return fmt.Errorf("error creating algorithm file: %v", err) return fmt.Errorf("error staging algorithm file: %w", err)
}
if _, err := f.Write(algoData); err != nil {
return fmt.Errorf("error writing algorithm to file: %v", err)
}
if err := os.Chmod(f.Name(), algoFilePermission); err != nil {
return fmt.Errorf("error changing file permissions: %v", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %v", err)
} }
algoType := algorithm.AlgorithmTypeFromContext(ctx) algoType := algorithm.AlgorithmTypeFromContext(ctx)
@@ -1079,7 +1104,13 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
as.algoType = algoType as.algoType = algoType
as.algoArgs = args as.algoArgs = args
as.algoRequirements = algo.Requirements if len(as.algoRequirements) == 0 {
as.algoRequirements = algo.Requirements
}
if _, err := stageRequirementsFile(currentDir, as.algoRequirements); err != nil {
return fmt.Errorf("error staging requirements file: %w", err)
}
as.algoReceived = true as.algoReceived = true
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil { if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
@@ -1276,9 +1307,9 @@ func (as *agentService) runComputation(state statemachine.State) {
} }
}() }()
// Read algo file
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
algoFile := filepath.Join(currentDir, "algo") algoFile := filepath.Join(currentDir, algoFileName)
requirementsFile := filepath.Join(currentDir, requirementsFileName)
defer func() { defer func() {
if err := os.RemoveAll(algorithm.ResultsDir); err != nil { if err := os.RemoveAll(algorithm.ResultsDir); err != nil {
@@ -1290,6 +1321,9 @@ func (as *agentService) runComputation(state statemachine.State) {
if err := os.Remove(algoFile); err != nil && !os.IsNotExist(err) { if err := os.Remove(algoFile); err != nil && !os.IsNotExist(err) {
as.logger.Warn(fmt.Sprintf("error removing algorithm file: %s", err.Error())) as.logger.Warn(fmt.Sprintf("error removing algorithm file: %s", err.Error()))
} }
if err := os.Remove(requirementsFile); err != nil && !os.IsNotExist(err) {
as.logger.Warn(fmt.Sprintf("error removing requirements file: %s", err.Error()))
}
}() }()
if err := ensureDir(algorithm.ResultsDir, 0o755); err != nil { if err := ensureDir(algorithm.ResultsDir, 0o755); err != nil {
@@ -1301,10 +1335,21 @@ func (as *agentService) runComputation(state statemachine.State) {
return return
} }
algoBytes, err := os.ReadFile(algoFile) if _, err := os.Stat(algoFile); err != nil {
if err != nil {
as.mu.Lock() as.mu.Lock()
as.runError = fmt.Errorf("failed to read algo file: %w", err) as.runError = fmt.Errorf("failed to stat algo file: %w", err)
as.mu.Unlock()
as.logger.Warn(as.runError.Error())
as.publishEvent(Failed.String())(state)
return
}
requirementsPath := ""
if _, err := os.Stat(requirementsFile); err == nil {
requirementsPath = requirementsFile
} else if !os.IsNotExist(err) {
as.mu.Lock()
as.runError = fmt.Errorf("failed to stat requirements file: %w", err)
as.mu.Unlock() as.mu.Unlock()
as.logger.Warn(as.runError.Error()) as.logger.Warn(as.runError.Error())
as.publishEvent(Failed.String())(state) as.publishEvent(Failed.String())(state)
@@ -1315,11 +1360,11 @@ func (as *agentService) runComputation(state statemachine.State) {
// Call Runner // Call Runner
resp, err := as.runnerClient.Run(context.Background(), &runnerpb.RunRequest{ resp, err := as.runnerClient.Run(context.Background(), &runnerpb.RunRequest{
ComputationId: as.computation.ID, ComputationId: as.computation.ID,
AlgoType: as.algoType, AlgoType: as.algoType,
Algorithm: algoBytes, AlgorithmPath: algoFile,
Requirements: as.algoRequirements, RequirementsPath: requirementsPath,
Args: as.algoArgs, Args: as.algoArgs,
// Datasets implicit on shared FS // Datasets implicit on shared FS
}) })
if err != nil { if err != nil {
+4 -2
View File
@@ -1014,7 +1014,7 @@ func TestRunComputation(t *testing.T) {
svc.runComputation(Running) svc.runComputation(Running)
assert.Error(t, svc.runError) assert.Error(t, svc.runError)
assert.Contains(t, svc.runError.Error(), "failed to read algo file") assert.Contains(t, svc.runError.Error(), "failed to stat algo file")
sm.AssertExpectations(t) sm.AssertExpectations(t)
}) })
@@ -1026,7 +1026,9 @@ func TestRunComputation(t *testing.T) {
require.NoError(t, os.WriteFile("algo", []byte("#!/bin/sh\necho ok\n"), 0o755)) require.NoError(t, os.WriteFile("algo", []byte("#!/bin/sh\necho ok\n"), 0o755))
runnerCli := new(runnermocks.Client) runnerCli := new(runnermocks.Client)
runnerCli.On("Run", mock.Anything, mock.Anything).Return((*runnerpb.RunResponse)(nil), fmt.Errorf("runner unavailable")) runnerCli.On("Run", mock.Anything, mock.MatchedBy(func(req *runnerpb.RunRequest) bool {
return req != nil && filepath.Base(req.AlgorithmPath) == algoFileName && req.RequirementsPath == "" && len(req.Args) == 0
})).Return((*runnerpb.RunResponse)(nil), fmt.Errorf("runner unavailable"))
eventsSvc := new(mocks.Service) eventsSvc := new(mocks.Service)
eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe() eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
+10 -1
View File
@@ -15,6 +15,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time"
"github.com/absmach/certs/sdk" "github.com/absmach/certs/sdk"
mglog "github.com/absmach/magistrala/logger" mglog "github.com/absmach/magistrala/logger"
@@ -274,7 +275,15 @@ func main() {
}) })
g.Go(func() error { g.Go(func() error {
return mc.Process(ctx, cancel) for {
if err := mc.Process(ctx, cancel); err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
logger.Warn("CVMS client process connection loop exited, retrying in 2s...", "error", err)
time.Sleep(2 * time.Second)
}
}
}) })
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc, ccPlatform) attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc, ccPlatform)
+2 -2
View File
@@ -38,9 +38,9 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-image.
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)" BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
# Linux headers same as kernel # Linux headers same as kernel
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_7_0=y
BR2_TOOLCHAIN_HEADERS_LATEST=y BR2_TOOLCHAIN_HEADERS_LATEST=y
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7" BR2_TOOLCHAIN_HEADERS_AT_LEAST="7.0"
# Kernel # Kernel
BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL=y
+43
View File
@@ -1,8 +1,18 @@
CONFIG_SYSVIPC=y CONFIG_SYSVIPC=y
CONFIG_CGROUPS=y CONFIG_CGROUPS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_PIDS=y
CONFIG_MEMCG=y
CONFIG_BLK_CGROUP=y
CONFIG_MODULES=y CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y CONFIG_MODULE_UNLOAD=y
CONFIG_SMP=y CONFIG_SMP=y
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_HYPERVISOR_GUEST=y CONFIG_HYPERVISOR_GUEST=y
CONFIG_PARAVIRT=y CONFIG_PARAVIRT=y
CONFIG_NET=y CONFIG_NET=y
@@ -14,12 +24,17 @@ CONFIG_PCI=y
CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y CONFIG_DEVTMPFS_MOUNT=y
CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_BLK=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_LOOP_MIN_COUNT=8
CONFIG_BLK_DEV_SD=y CONFIG_BLK_DEV_SD=y
CONFIG_SCSI_VIRTIO=y CONFIG_SCSI_VIRTIO=y
CONFIG_ATA=y CONFIG_ATA=y
CONFIG_ATA_PIIX=y CONFIG_ATA_PIIX=y
CONFIG_NETDEVICES=y CONFIG_NETDEVICES=y
CONFIG_VIRTIO_NET=y CONFIG_VIRTIO_NET=y
CONFIG_VETH=y
CONFIG_BRIDGE=y
CONFIG_BRIDGE_NETFILTER=y
CONFIG_NE2K_PCI=y CONFIG_NE2K_PCI=y
CONFIG_8139CP=y CONFIG_8139CP=y
# CONFIG_WLAN is not set # CONFIG_WLAN is not set
@@ -34,15 +49,43 @@ CONFIG_VIRTIO_INPUT=y
CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
CONFIG_EXT4_FS=y CONFIG_EXT4_FS=y
CONFIG_OVERLAY_FS=y
CONFIG_AUTOFS4_FS=y CONFIG_AUTOFS4_FS=y
CONFIG_TMPFS=y CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y CONFIG_TMPFS_POSIX_ACL=y
CONFIG_FHANDLE=y
CONFIG_INOTIFY_USER=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EPOLL=y
CONFIG_POSIX_MQUEUE=y
CONFIG_POSIX_MQUEUE_SYSCTL=y
CONFIG_UNWINDER_FRAME_POINTER=y CONFIG_UNWINDER_FRAME_POINTER=y
CONFIG_VSOCKETS=y CONFIG_VSOCKETS=y
CONFIG_VIRTIO_VSOCKETS=y CONFIG_VIRTIO_VSOCKETS=y
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NETFILTER_XTABLES_LEGACY=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_CONNTRACK_MARK=y
CONFIG_NF_NAT=y
CONFIG_NF_NAT_MASQUERADE=y
CONFIG_NETFILTER_XTABLES=y
CONFIG_NETFILTER_XT_NAT=y
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
CONFIG_NF_DEFRAG_IPV4=y
CONFIG_IP_NF_IPTABLES_LEGACY=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_NAT=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NF_TABLES=y CONFIG_NF_TABLES=y
CONFIG_BPF_SYSCALL=y CONFIG_BPF_SYSCALL=y
CONFIG_CGROUP_BPF=y CONFIG_CGROUP_BPF=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
### ###
# AMD SEV-SNP # AMD SEV-SNP
+2 -2
View File
@@ -25,9 +25,9 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-image.
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)" BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
# Linux headers same as kernel # Linux headers same as kernel
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_7_0=y
BR2_TOOLCHAIN_HEADERS_LATEST=y BR2_TOOLCHAIN_HEADERS_LATEST=y
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7" BR2_TOOLCHAIN_HEADERS_AT_LEAST="7.0"
# Kernel # Kernel
BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL=y
+6
View File
@@ -22,6 +22,12 @@ define AGENT_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0640 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service $(INSTALL) -D -m 0640 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_setup.sh $(TARGET_DIR)/cocos_init/agent_setup.sh $(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_setup.sh $(TARGET_DIR)/cocos_init/agent_setup.sh
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos_init/agent_start_script.sh $(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos_init/agent_start_script.sh
mkdir -p $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants
ln -sf ../docker.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/docker.service
ln -sf ../log-forwarder.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/log-forwarder.service
ln -sf ../computation-runner.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/computation-runner.service
ln -sf ../egress-proxy.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/egress-proxy.service
ln -sf ../cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/cocos-agent.service
endef endef
$(eval $(generic-package)) $(eval $(generic-package))
+18
View File
@@ -1,6 +1,24 @@
#!/bin/sh #!/bin/sh
WORK_DIR="/cocos" WORK_DIR="/cocos"
DOCKER_OVERRIDE_DIR="/etc/systemd/system/docker.service.d"
DOCKER_OVERRIDE_FILE="$DOCKER_OVERRIDE_DIR/override.conf"
# Docker runs on a writable bind mount while the rootfs itself stays ephemeral,
# so force RAM-disk mode to avoid pivot_root failures inside containers.
mkdir -p "$DOCKER_OVERRIDE_DIR"
if ! grep -qs '^Environment=DOCKER_RAMDISK=true$' "$DOCKER_OVERRIDE_FILE"; then
tee "$DOCKER_OVERRIDE_FILE" > /dev/null <<EOF
[Service]
Environment=DOCKER_RAMDISK=true
EOF
systemctl daemon-reload
fi
# Docker is required by the computation runner. Start the runtime stack here so
# later units don't depend on passive boot enablement or service ordering races.
systemctl start containerd.service
systemctl start docker.service
# IFACES are all network interfaces excluding lo (LOOPBACK) and sit interfaces # IFACES are all network interfaces excluding lo (LOOPBACK) and sit interfaces
IFACES=$(ip link show | grep -vE 'LOOPBACK|sit*' | awk -F': ' '{print $2}') IFACES=$(ip link show | grep -vE 'LOOPBACK|sit*' | awk -F': ' '{print $2}')
+1 -2
View File
@@ -2,7 +2,6 @@
Description=Cocos AI agent Description=Cocos AI agent
After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service
Requires=log-forwarder.service computation-runner.service egress-proxy.service Requires=log-forwarder.service computation-runner.service egress-proxy.service
Before=docker.service
[Service] [Service]
WorkingDirectory=/cocos WorkingDirectory=/cocos
@@ -17,4 +16,4 @@ Restart=always
RestartSec=5s RestartSec=5s
[Install] [Install]
WantedBy=default.target WantedBy=multi-user.target
+3 -2
View File
@@ -1,8 +1,9 @@
[Unit] [Unit]
Description=Cocos AI Computation Runner Description=Cocos AI Computation Runner
After=network.target log-forwarder.service After=network.target log-forwarder.service docker.service
Before=cocos-agent.service Before=cocos-agent.service
Requires=log-forwarder.service Requires=log-forwarder.service
Wants=docker.service
[Service] [Service]
WorkingDirectory=/cocos WorkingDirectory=/cocos
@@ -17,4 +18,4 @@ Restart=always
RestartSec=5s RestartSec=5s
[Install] [Install]
WantedBy=default.target WantedBy=multi-user.target
+1 -1
View File
@@ -14,4 +14,4 @@ Restart=always
RestartSec=5s RestartSec=5s
[Install] [Install]
WantedBy=default.target WantedBy=multi-user.target
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: internal/proto/attestation-agent/attestation-agent.proto // source: internal/proto/attestation-agent/attestation-agent.proto
package attestation_agent package attestation_agent
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: internal/proto/attestation-agent/attestation-agent.proto // source: internal/proto/attestation-agent/attestation-agent.proto
package attestation_agent package attestation_agent
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: internal/proto/attestation/v1/attestation.proto // source: internal/proto/attestation/v1/attestation.proto
package attestation package attestation
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: internal/proto/attestation/v1/attestation.proto // source: internal/proto/attestation/v1/attestation.proto
package attestation package attestation
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v7.35.1
// source: manager/manager.proto // source: manager/manager.proto
package manager package manager
+1 -1
View File
@@ -4,7 +4,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.0
// - protoc v7.35.0 // - protoc v7.35.1
// source: manager/manager.proto // source: manager/manager.proto
package manager package manager