mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-08-07 23:31:55 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c502e1a1 | |||
| bdbeb4e976 | |||
| c402248515 | |||
| 7155027440 | |||
| 4c80b574e3 | |||
| ee8370406c | |||
| f906593492 | |||
| 899bfb0ec5 | |||
| e4ef1aae36 | |||
| 6aec555f4e | |||
| 066dacd46a | |||
| 8f2cbd349d | |||
| f596702e6e | |||
| 31391a36a1 | |||
| 971e00aa68 | |||
| 18cfa7619e | |||
| afc306a85b | |||
| 3c855e3b68 | |||
| 9161d30683 |
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Set up protoc
|
||||
run: |
|
||||
PROTOC_VERSION=25.3
|
||||
PROTOC_VERSION=27.2
|
||||
PROTOC_GEN_VERSION=v1.34.2
|
||||
PROTOC_GRPC_VERSION=v1.4.0
|
||||
|
||||
|
||||
+1
-3
@@ -2,13 +2,11 @@ build
|
||||
|
||||
build
|
||||
cmd/manager/img
|
||||
cmd/manager/iso
|
||||
cmd/manager/tmp
|
||||
|
||||
.cov
|
||||
|
||||
*.pem
|
||||
|
||||
dist/
|
||||
result.bin
|
||||
results.zip
|
||||
*.spec
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
BUILD_DIR = build
|
||||
SERVICES = manager agent cli
|
||||
PLATFORM_INFO = platform_info
|
||||
BACKEND_INFO = backend_info
|
||||
CGO_ENABLED ?= 0
|
||||
GOARCH ?= amd64
|
||||
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
|
||||
COMMIT ?= $(shell git rev-parse HEAD)
|
||||
TIME ?= $(shell date +%F_%T)
|
||||
EMBED_ENABLED ?= 0
|
||||
|
||||
define compile_service
|
||||
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
|
||||
@@ -13,18 +14,19 @@ define compile_service
|
||||
-X 'github.com/absmach/magistrala.BuildTime=$(TIME)' \
|
||||
-X 'github.com/absmach/magistrala.Version=$(VERSION)' \
|
||||
-X 'github.com/absmach/magistrala.Commit=$(COMMIT)'" \
|
||||
$(if $(filter 1,$(EMBED_ENABLED)),-tags "embed",) \
|
||||
-o ${BUILD_DIR}/cocos-$(1) cmd/$(1)/main.go
|
||||
endef
|
||||
|
||||
.PHONY: all $(SERVICES) $(PLATFORM_INFO)
|
||||
.PHONY: all $(SERVICES) $(BACKEND_INFO)
|
||||
|
||||
all: $(SERVICES)
|
||||
|
||||
$(SERVICES):
|
||||
$(call compile_service,$(@))
|
||||
$(call compile_service,$@)
|
||||
|
||||
$(PLATFORM_INFO):
|
||||
$(MAKE) -C ./scripts/platform_info
|
||||
$(BACKEND_INFO):
|
||||
$(MAKE) -C ./scripts/backend_info
|
||||
|
||||
protoc:
|
||||
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/agent.proto
|
||||
|
||||
+41
-31
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v4.25.3
|
||||
// protoc v5.27.2
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
@@ -121,7 +121,8 @@ type DataRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
Filename string `protobuf:"bytes,2,opt,name=filename,proto3" json:"filename,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DataRequest) Reset() {
|
||||
@@ -163,6 +164,13 @@ func (x *DataRequest) GetDataset() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DataRequest) GetFilename() string {
|
||||
if x != nil {
|
||||
return x.Filename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DataResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -390,37 +398,39 @@ var file_agent_agent_proto_rawDesc = []byte{
|
||||
0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x69,
|
||||
0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72,
|
||||
0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x44,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x0b, 0x44,
|
||||
0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x61,
|
||||
0x74, 0x61, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x61, 0x74,
|
||||
0x61, 0x73, 0x65, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61,
|
||||
0x74, 0x61, 0x22, 0x29, 0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfb, 0x01,
|
||||
0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33,
|
||||
0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75,
|
||||
0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
|
||||
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e,
|
||||
0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x61, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x29,
|
||||
0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfb, 0x01, 0x0a, 0x0c, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12,
|
||||
0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73,
|
||||
0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
|
||||
0x46, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -23,6 +23,7 @@ message AlgoResponse {}
|
||||
|
||||
message DataRequest {
|
||||
bytes dataset = 1;
|
||||
string filename = 2;
|
||||
}
|
||||
|
||||
message DataResponse {}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v4.25.3
|
||||
// - protoc v5.27.2
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
|
||||
@@ -13,7 +13,14 @@ type AlgorithType string
|
||||
const (
|
||||
AlgoTypeBin AlgorithType = "bin"
|
||||
AlgoTypePython AlgorithType = "python"
|
||||
AlgoTypeWasm AlgorithType = "wasm"
|
||||
AlgoTypeDocker AlgorithType = "docker"
|
||||
AlgoTypeKey = "algo_type"
|
||||
AlgoArgsKey = "algo_args"
|
||||
|
||||
ResultsDir = "results"
|
||||
DatasetsDir = "datasets"
|
||||
AlgoWorkingDir = "/cocos"
|
||||
)
|
||||
|
||||
func AlgorithmTypeToContext(ctx context.Context, algoType string) context.Context {
|
||||
@@ -24,11 +31,19 @@ func AlgorithmTypeFromContext(ctx context.Context) string {
|
||||
return metadata.ValueFromIncomingContext(ctx, AlgoTypeKey)[0]
|
||||
}
|
||||
|
||||
func AlgorithmArgsToContext(ctx context.Context, algoArgs []string) context.Context {
|
||||
for _, arg := range algoArgs {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, AlgoArgsKey, arg)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func AlgorithmArgsFromContext(ctx context.Context) []string {
|
||||
return metadata.ValueFromIncomingContext(ctx, AlgoArgsKey)
|
||||
}
|
||||
|
||||
// Algorithm is an interface that specifies the API for an algorithm.
|
||||
type Algorithm interface {
|
||||
// Run executes the algorithm and returns the result.
|
||||
Run() ([]byte, error)
|
||||
|
||||
// Add dataset to algorithm.
|
||||
AddDataset(dataset string)
|
||||
Run() error
|
||||
}
|
||||
|
||||
@@ -6,77 +6,42 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/pkg/socket"
|
||||
)
|
||||
|
||||
const socketPath = "unix_socket"
|
||||
|
||||
var _ algorithm.Algorithm = (*binary)(nil)
|
||||
|
||||
type binary struct {
|
||||
algoFile string
|
||||
datasets []string
|
||||
logger *slog.Logger
|
||||
stderr io.Writer
|
||||
stdout io.Writer
|
||||
args []string
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, eventsSvc events.Service, algoFile string) algorithm.Algorithm {
|
||||
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string, args []string) algorithm.Algorithm {
|
||||
return &binary{
|
||||
algoFile: algoFile,
|
||||
logger: logger,
|
||||
stderr: &algorithm.Stderr{Logger: logger, EventSvc: eventsSvc},
|
||||
stdout: &algorithm.Stdout{Logger: logger},
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *binary) AddDataset(dataset string) {
|
||||
b.datasets = append(b.datasets, dataset)
|
||||
}
|
||||
|
||||
func (b *binary) Run() ([]byte, error) {
|
||||
defer os.Remove(b.algoFile)
|
||||
defer func() {
|
||||
for _, file := range b.datasets {
|
||||
os.Remove(file)
|
||||
}
|
||||
}()
|
||||
listener, err := socket.StartUnixSocketServer(socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating stdout pipe: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
// Create channels for received data and errors
|
||||
dataChannel := make(chan []byte)
|
||||
errorChannel := make(chan error)
|
||||
|
||||
var result []byte
|
||||
|
||||
go socket.AcceptConnection(listener, dataChannel, errorChannel)
|
||||
|
||||
args := append([]string{socketPath}, b.datasets...)
|
||||
cmd := exec.Command(b.algoFile, args...)
|
||||
func (b *binary) Run() error {
|
||||
cmd := exec.Command(b.algoFile, b.args...)
|
||||
cmd.Stderr = b.stderr
|
||||
cmd.Stdout = b.stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("error starting algorithm: %v", err)
|
||||
return fmt.Errorf("error starting algorithm: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("algorithm execution error: %v", err)
|
||||
return fmt.Errorf("algorithm execution error: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case result = <-dataChannel:
|
||||
return result, nil
|
||||
case err = <-errorChannel:
|
||||
return nil, fmt.Errorf("error receiving data: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
)
|
||||
|
||||
const (
|
||||
containerName = "agent_container"
|
||||
datasetsMountPath = "/cocos/datasets"
|
||||
resultsMountPath = "/cocos/results"
|
||||
)
|
||||
|
||||
var _ algorithm.Algorithm = (*docker)(nil)
|
||||
|
||||
type docker struct {
|
||||
algoFile string
|
||||
logger *slog.Logger
|
||||
stderr io.Writer
|
||||
stdout io.Writer
|
||||
}
|
||||
|
||||
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string) algorithm.Algorithm {
|
||||
d := &docker{
|
||||
algoFile: algoFile,
|
||||
logger: logger,
|
||||
stderr: &algorithm.Stderr{Logger: logger, EventSvc: eventsSvc},
|
||||
stdout: &algorithm.Stdout{Logger: logger},
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *docker) Run() error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a new Docker client.
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create a new Docker client: %v", err)
|
||||
}
|
||||
|
||||
// Open the Docker image tar file.
|
||||
imageFile, err := os.Open(d.algoFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open Docker image: %v", err)
|
||||
}
|
||||
defer imageFile.Close()
|
||||
|
||||
// Load the Docker image from the tar file.
|
||||
resp, err := cli.ImageLoad(ctx, imageFile, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load Docker image from file: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// List the loaded images to get the image ID.
|
||||
images, err := cli.ImageList(ctx, image.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get the Docker image list: %v", err)
|
||||
}
|
||||
|
||||
var imageID string = ""
|
||||
var dockerImageName string = ""
|
||||
for _, image := range images {
|
||||
for _, tag := range image.RepoTags {
|
||||
imageID = image.ID
|
||||
dockerImageName = tag
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if imageID == "" {
|
||||
return fmt.Errorf("could not find image ID")
|
||||
}
|
||||
|
||||
// Create and start the container.
|
||||
respContainer, err := cli.ContainerCreate(ctx, &container.Config{
|
||||
Image: dockerImageName,
|
||||
}, &container.HostConfig{
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
Type: mount.TypeBind,
|
||||
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.DatasetsDir),
|
||||
Target: datasetsMountPath,
|
||||
},
|
||||
{
|
||||
Type: mount.TypeBind,
|
||||
Source: path.Join(algorithm.AlgoWorkingDir, algorithm.ResultsDir),
|
||||
Target: resultsMountPath,
|
||||
},
|
||||
},
|
||||
}, nil, nil, containerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create a Docker container: %v", err)
|
||||
}
|
||||
|
||||
if err := cli.ContainerStart(ctx, respContainer.ID, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("could not start a Docker container: %v", err)
|
||||
}
|
||||
|
||||
statusCh, errCh := cli.ContainerWait(ctx, respContainer.ID, container.WaitConditionNotRunning)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not wait for a Docker container: %v", err)
|
||||
}
|
||||
case <-statusCh:
|
||||
}
|
||||
|
||||
stdout, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStdout: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read stdout from the container: %v", err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
|
||||
err = writeToOut(stdout, d.stdout)
|
||||
if err != nil {
|
||||
d.logger.Warn(fmt.Sprintf("could not write to stdout: %v", err))
|
||||
}
|
||||
|
||||
stderr, err := cli.ContainerLogs(ctx, respContainer.ID, container.LogsOptions{ShowStderr: true})
|
||||
if err != nil {
|
||||
d.logger.Warn(fmt.Sprintf("could not read stderr from the container: %v", err))
|
||||
}
|
||||
defer stderr.Close()
|
||||
|
||||
err = writeToOut(stderr, d.stderr)
|
||||
if err != nil {
|
||||
d.logger.Warn(fmt.Sprintf("could not write to stderr: %v", err))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = cli.ContainerRemove(ctx, respContainer.ID, container.RemoveOptions{Force: true}); err != nil {
|
||||
d.logger.Warn(fmt.Sprintf("error could not remove container: %v", err))
|
||||
}
|
||||
|
||||
if _, err := cli.ImageRemove(ctx, imageID, image.RemoveOptions{Force: true}); err != nil {
|
||||
d.logger.Warn(fmt.Sprintf("error could not remove image: %v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeToOut(readCloser io.ReadCloser, ioWriter io.Writer) error {
|
||||
content, err := io.ReadAll(readCloser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert content from the container: %v", err)
|
||||
}
|
||||
|
||||
if _, err := ioWriter.Write(content); err != nil {
|
||||
return fmt.Errorf("could not write to output: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -13,12 +13,10 @@ import (
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/pkg/socket"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
socketPath = "unix_socket"
|
||||
PyRuntime = "python3"
|
||||
pyRuntimeKey = "python_runtime"
|
||||
)
|
||||
@@ -35,21 +33,20 @@ var _ algorithm.Algorithm = (*python)(nil)
|
||||
|
||||
type python struct {
|
||||
algoFile string
|
||||
datasets []string
|
||||
logger *slog.Logger
|
||||
stderr io.Writer
|
||||
stdout io.Writer
|
||||
runtime string
|
||||
requirementsFile string
|
||||
args []string
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, eventsSvc events.Service, runtime, requirementsFile, algoFile string) algorithm.Algorithm {
|
||||
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, runtime, requirementsFile, algoFile string, args []string) algorithm.Algorithm {
|
||||
p := &python{
|
||||
algoFile: algoFile,
|
||||
logger: logger,
|
||||
stderr: &algorithm.Stderr{Logger: logger, EventSvc: eventsSvc},
|
||||
stdout: &algorithm.Stdout{Logger: logger},
|
||||
requirementsFile: requirementsFile,
|
||||
args: args,
|
||||
}
|
||||
if runtime != "" {
|
||||
p.runtime = runtime
|
||||
@@ -59,17 +56,13 @@ func New(logger *slog.Logger, eventsSvc events.Service, runtime, requirementsFil
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *python) AddDataset(dataset string) {
|
||||
p.datasets = append(p.datasets, dataset)
|
||||
}
|
||||
|
||||
func (p *python) Run() ([]byte, error) {
|
||||
func (p *python) Run() error {
|
||||
venvPath := "venv"
|
||||
createVenvCmd := exec.Command(p.runtime, "-m", "venv", venvPath)
|
||||
createVenvCmd.Stderr = p.stderr
|
||||
createVenvCmd.Stdout = p.stdout
|
||||
if err := createVenvCmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("error creating virtual environment: %v", err)
|
||||
return fmt.Errorf("error creating virtual environment: %v", err)
|
||||
}
|
||||
|
||||
pythonPath := filepath.Join(venvPath, "bin", "python")
|
||||
@@ -79,48 +72,26 @@ func (p *python) Run() ([]byte, error) {
|
||||
rcmd.Stderr = p.stderr
|
||||
rcmd.Stdout = p.stdout
|
||||
if err := rcmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("error installing requirements: %v", err)
|
||||
return fmt.Errorf("error installing requirements: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
defer os.Remove(p.algoFile)
|
||||
defer func() {
|
||||
for _, file := range p.datasets {
|
||||
os.Remove(file)
|
||||
}
|
||||
}()
|
||||
defer os.RemoveAll(venvPath)
|
||||
|
||||
listener, err := socket.StartUnixSocketServer(socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating stdout pipe: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
dataChannel := make(chan []byte)
|
||||
errorChannel := make(chan error)
|
||||
|
||||
var result []byte
|
||||
|
||||
go socket.AcceptConnection(listener, dataChannel, errorChannel)
|
||||
|
||||
args := append([]string{p.algoFile, socketPath}, p.datasets...)
|
||||
args := append([]string{p.algoFile}, p.args...)
|
||||
cmd := exec.Command(pythonPath, args...)
|
||||
cmd.Stderr = p.stderr
|
||||
cmd.Stdout = p.stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("error starting algorithm: %v", err)
|
||||
return fmt.Errorf("error starting algorithm: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("algorithm execution error: %v", err)
|
||||
return fmt.Errorf("algorithm execution error: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case result = <-dataChannel:
|
||||
return result, nil
|
||||
case err = <-errorChannel:
|
||||
return nil, fmt.Errorf("error receiving data: %v", err)
|
||||
if err := os.RemoveAll(venvPath); err != nil {
|
||||
return fmt.Errorf("error removing virtual environment: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package algorithm_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
)
|
||||
|
||||
func TestZipDirectory(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
directories []string
|
||||
files []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty directory",
|
||||
directories: []string{"testdata"},
|
||||
},
|
||||
{
|
||||
name: "single file",
|
||||
files: []string{"file1.txt"},
|
||||
},
|
||||
{
|
||||
name: "directory with single file",
|
||||
directories: []string{"testdata"},
|
||||
expected: []string{"testdata/file1.txt"},
|
||||
},
|
||||
{
|
||||
name: "directory with multiple files",
|
||||
directories: []string{"testdata"},
|
||||
expected: []string{
|
||||
"testdata/file1.txt",
|
||||
"testdata/file2.txt",
|
||||
"testdata/file3.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested directories",
|
||||
directories: []string{"testdata", "testdata/nested"},
|
||||
expected: []string{
|
||||
"testdata/nested/file1.txt",
|
||||
"testdata/nested/file2.txt",
|
||||
"testdata/nested/file3.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := os.Mkdir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
t.Fatalf("error creating results directory: %s", err.Error())
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(algorithm.ResultsDir); err != nil {
|
||||
t.Fatalf("error removing results directory and its contents: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
for _, dir := range tc.directories {
|
||||
if dir != "" {
|
||||
if err := os.Mkdir(algorithm.ResultsDir+"/"+dir, 0o755); err != nil {
|
||||
t.Fatalf("error creating test directory: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, file := range tc.files {
|
||||
if _, err := os.Create(algorithm.ResultsDir + "/" + file); err != nil {
|
||||
t.Fatalf("error creating test file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := internal.ZipDirectoryToMemory(algorithm.ResultsDir); err != nil {
|
||||
t.Errorf("ZipDirectory() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
)
|
||||
|
||||
const wasmRuntime = "wasmedge"
|
||||
|
||||
var mapDirOption = []string{"--dir", ".:" + algorithm.ResultsDir}
|
||||
|
||||
var _ algorithm.Algorithm = (*wasm)(nil)
|
||||
|
||||
type wasm struct {
|
||||
algoFile string
|
||||
stderr io.Writer
|
||||
stdout io.Writer
|
||||
args []string
|
||||
}
|
||||
|
||||
func NewAlgorithm(logger *slog.Logger, eventsSvc events.Service, algoFile string, args []string) algorithm.Algorithm {
|
||||
return &wasm{
|
||||
algoFile: algoFile,
|
||||
stderr: &algorithm.Stderr{Logger: logger, EventSvc: eventsSvc},
|
||||
stdout: &algorithm.Stdout{Logger: logger},
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wasm) Run() error {
|
||||
args := append(mapDirOption, w.algoFile)
|
||||
args = append(args, w.args...)
|
||||
cmd := exec.Command(wasmRuntime, args...)
|
||||
cmd.Stderr = w.stderr
|
||||
cmd.Stdout = w.stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("error starting algorithm: %v", err)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("algorithm execution error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func dataEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return dataRes{}, err
|
||||
}
|
||||
|
||||
dataset := agent.Dataset{Dataset: req.Dataset}
|
||||
dataset := agent.Dataset{Dataset: req.Dataset, Filename: req.Filename}
|
||||
|
||||
err := svc.Data(ctx, dataset)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,7 +19,8 @@ func (req algoReq) validate() error {
|
||||
}
|
||||
|
||||
type dataReq struct {
|
||||
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (req dataReq) validate() error {
|
||||
|
||||
@@ -69,7 +69,8 @@ func decodeDataRequest(_ context.Context, grpcReq interface{}) (interface{}, err
|
||||
req := grpcReq.(*agent.DataRequest)
|
||||
|
||||
return dataReq{
|
||||
Dataset: req.Dataset,
|
||||
Dataset: req.Dataset,
|
||||
Filename: req.Filename,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -128,6 +129,7 @@ func (s *grpcServer) Algo(stream agent.AgentService_AlgoServer) error {
|
||||
// Data implements agent.AgentServiceServer.
|
||||
func (s *grpcServer) Data(stream agent.AgentService_DataServer) error {
|
||||
var dataFile []byte
|
||||
var filename string
|
||||
for {
|
||||
dataChunk, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
@@ -137,8 +139,9 @@ func (s *grpcServer) Data(stream agent.AgentService_DataServer) error {
|
||||
return status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
dataFile = append(dataFile, dataChunk.Dataset...)
|
||||
filename = dataChunk.Filename
|
||||
}
|
||||
_, res, err := s.data.ServeGRPC(stream.Context(), &agent.DataRequest{Dataset: dataFile})
|
||||
_, res, err := s.data.ServeGRPC(stream.Context(), &agent.DataRequest{Dataset: dataFile, Filename: filename})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-2
@@ -146,9 +146,9 @@ func (s *service) AuthenticateUser(ctx context.Context, role UserRole) (context.
|
||||
}
|
||||
}
|
||||
case DataProviderRole:
|
||||
for i, dp := range s.datasetProviders {
|
||||
for _, dp := range s.datasetProviders {
|
||||
if err := verifySignature(role, signature, dp); err == nil {
|
||||
return agent.IndexToContext(ctx, i), nil
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
case AlgorithmProviderRole:
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
|
||||
if err == nil {
|
||||
switch id, ok := agent.IndexFromContext(ctx); {
|
||||
case tc.role == ConsumerRole, tc.role == DataProviderRole:
|
||||
case tc.role == ConsumerRole:
|
||||
assert.True(t, ok, "expected index in context")
|
||||
assert.Equal(t, 0, id, "expected index 0 in context")
|
||||
default:
|
||||
|
||||
+21
-3
@@ -6,6 +6,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var _ fmt.Stringer = (*Datasets)(nil)
|
||||
@@ -44,9 +46,10 @@ func (d *Datasets) String() string {
|
||||
}
|
||||
|
||||
type Dataset struct {
|
||||
Dataset []byte `json:"-"`
|
||||
Hash [32]byte `json:"hash,omitempty"`
|
||||
UserKey []byte `json:"user_key,omitempty"`
|
||||
Dataset []byte `json:"-"`
|
||||
Hash [32]byte `json:"hash,omitempty"`
|
||||
UserKey []byte `json:"user_key,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
}
|
||||
|
||||
type Datasets []Dataset
|
||||
@@ -68,3 +71,18 @@ func IndexFromContext(ctx context.Context) (int, bool) {
|
||||
index, ok := ctx.Value(ManifestIndexKey{}).(int)
|
||||
return index, ok
|
||||
}
|
||||
|
||||
const DecompressKey = "decompress"
|
||||
|
||||
func DecompressFromContext(ctx context.Context) bool {
|
||||
vals := metadata.ValueFromIncomingContext(ctx, DecompressKey)
|
||||
if len(vals) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return vals[0] == "true"
|
||||
}
|
||||
|
||||
func DecompressToContext(ctx context.Context, decompress bool) context.Context {
|
||||
return metadata.AppendToOutgoingContext(ctx, DecompressKey, fmt.Sprintf("%t", decompress))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.42.3. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.42.3. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package quoteprovider
|
||||
|
||||
import (
|
||||
"github.com/google/go-sev-guest/client"
|
||||
pb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
cocosai "github.com/ultravioletrs/cocos"
|
||||
)
|
||||
|
||||
var _ client.QuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
|
||||
type embeddedQuoteProvider struct {
|
||||
}
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
return &embeddedQuoteProvider{}, nil
|
||||
}
|
||||
|
||||
// GetQuote returns the SEV quote for the given report data.
|
||||
func (e *embeddedQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
// IsSupported returns true if the SEV platform is supported.
|
||||
func (e *embeddedQuoteProvider) IsSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Product returns the SEV product information.
|
||||
// unimplemented since it is deprecated and not used.
|
||||
func (e *embeddedQuoteProvider) Product() *pb.SevProduct {
|
||||
panic("unimplemented")
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !embed
|
||||
// +build !embed
|
||||
|
||||
package quoteprovider
|
||||
|
||||
import "github.com/google/go-sev-guest/client"
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
return client.GetQuoteProvider()
|
||||
}
|
||||
+135
-64
@@ -10,13 +10,17 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/binary"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/docker"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/python"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/wasm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -35,18 +39,22 @@ var (
|
||||
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
|
||||
// when accessing a protected resource.
|
||||
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
||||
// errUndeclaredAlgorithm indicates algorithm was not declared in computation manifest.
|
||||
// ErrUndeclaredAlgorithm indicates algorithm was not declared in computation manifest.
|
||||
ErrUndeclaredDataset = errors.New("dataset not declared in computation manifest")
|
||||
// errAllManifestItemsReceived indicates no new computation manifest items expected.
|
||||
// ErrAllManifestItemsReceived indicates no new computation manifest items expected.
|
||||
ErrAllManifestItemsReceived = errors.New("all expected manifest Items have been received")
|
||||
// errUndeclaredConsumer indicates the consumer requesting results in not declared in computation manifest.
|
||||
// ErrUndeclaredConsumer indicates the consumer requesting results in not declared in computation manifest.
|
||||
ErrUndeclaredConsumer = errors.New("result consumer is undeclared in computation manifest")
|
||||
// errResultsNotReady indicates the computation results are not ready.
|
||||
// ErrResultsNotReady indicates the computation results are not ready.
|
||||
ErrResultsNotReady = errors.New("computation results are not yet ready")
|
||||
// errStateNotReady agent received a request in the wrong state.
|
||||
// ErrStateNotReady agent received a request in the wrong state.
|
||||
ErrStateNotReady = errors.New("agent not expecting this operation in the current state")
|
||||
// errHashMismatch provided algorithm/dataset does not match hash in manifest.
|
||||
// ErrHashMismatch provided algorithm/dataset does not match hash in manifest.
|
||||
ErrHashMismatch = errors.New("malformed data, hash does not match manifest")
|
||||
// ErrFileNameMismatch provided dataset filename does not match filename in manifest.
|
||||
ErrFileNameMismatch = errors.New("malformed data, filename does not match manifest")
|
||||
// ErrAllResultsConsumed indicates all results have been consumed.
|
||||
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
@@ -61,21 +69,23 @@ type Service interface {
|
||||
}
|
||||
|
||||
type agentService struct {
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
sm *StateMachine // Manages the state transitions of the agent service.
|
||||
runError error // Stores any error encountered during the computation run.
|
||||
eventSvc events.Service // Service for publishing events related to computation.
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
sm *StateMachine // Manages the state transitions of the agent service.
|
||||
runError error // Stores any error encountered during the computation run.
|
||||
eventSvc events.Service // Service for publishing events related to computation.
|
||||
quoteProvider client.QuoteProvider // Provider for generating attestation quotes.
|
||||
}
|
||||
|
||||
var _ Service = (*agentService)(nil)
|
||||
|
||||
// New instantiates the agent service implementation.
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp Computation) Service {
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp Computation, quoteProvider client.QuoteProvider) Service {
|
||||
svc := &agentService{
|
||||
sm: NewStateMachine(logger, cmp),
|
||||
eventSvc: eventSvc,
|
||||
sm: NewStateMachine(logger, cmp),
|
||||
eventSvc: eventSvc,
|
||||
quoteProvider: quoteProvider,
|
||||
}
|
||||
|
||||
go svc.sm.Start(ctx)
|
||||
@@ -87,8 +97,10 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp
|
||||
svc.sm.StateFunctions[resultsReady] = svc.publishEvent("in-progress", json.RawMessage{})
|
||||
svc.sm.StateFunctions[complete] = svc.publishEvent("in-progress", json.RawMessage{})
|
||||
svc.sm.StateFunctions[running] = svc.runComputation
|
||||
svc.sm.StateFunctions[failed] = svc.publishEvent("failed", json.RawMessage{})
|
||||
|
||||
svc.computation = cmp
|
||||
|
||||
svc.sm.SendEvent(manifestReceived)
|
||||
return svc
|
||||
}
|
||||
@@ -107,7 +119,12 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
return ErrHashMismatch
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "algorithm")
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current directory: %v", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(filepath.Join(currentDir, "algorithm"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating algorithm file: %v", err)
|
||||
}
|
||||
@@ -129,23 +146,37 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
algoType = string(algorithm.AlgoTypeBin)
|
||||
}
|
||||
|
||||
args := algorithm.AlgorithmArgsFromContext(ctx)
|
||||
|
||||
switch algoType {
|
||||
case string(algorithm.AlgoTypeBin):
|
||||
as.algorithm = binary.New(as.sm.logger, as.eventSvc, f.Name())
|
||||
as.algorithm = binary.NewAlgorithm(as.sm.logger, as.eventSvc, f.Name(), args)
|
||||
case string(algorithm.AlgoTypePython):
|
||||
fr, err := os.CreateTemp("", "requirements.txt")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating requirments file: %v", err)
|
||||
}
|
||||
var requirementsFile string
|
||||
if len(algo.Requirements) > 0 {
|
||||
fr, err := os.CreateTemp("", "requirements.txt")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating requirments file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := fr.Write(algo.Requirements); err != nil {
|
||||
return fmt.Errorf("error writing requirements to file: %v", err)
|
||||
}
|
||||
if err := fr.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
if _, err := fr.Write(algo.Requirements); err != nil {
|
||||
return fmt.Errorf("error writing requirements to file: %v", err)
|
||||
}
|
||||
if err := fr.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
}
|
||||
requirementsFile = fr.Name()
|
||||
}
|
||||
runtime := python.PythonRunTimeFromContext(ctx)
|
||||
as.algorithm = python.New(as.sm.logger, as.eventSvc, runtime, fr.Name(), f.Name())
|
||||
as.algorithm = python.NewAlgorithm(as.sm.logger, as.eventSvc, runtime, requirementsFile, f.Name(), args)
|
||||
case string(algorithm.AlgoTypeWasm):
|
||||
as.algorithm = wasm.NewAlgorithm(as.sm.logger, as.eventSvc, f.Name(), args)
|
||||
case string(algorithm.AlgoTypeDocker):
|
||||
as.algorithm = docker.NewAlgorithm(as.sm.logger, as.eventSvc, f.Name())
|
||||
}
|
||||
|
||||
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
return fmt.Errorf("error creating datasets directory: %v", err)
|
||||
}
|
||||
|
||||
if as.algorithm != nil {
|
||||
@@ -165,30 +196,42 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
|
||||
|
||||
hash := sha3.Sum256(dataset.Dataset)
|
||||
|
||||
index, ok := IndexFromContext(ctx)
|
||||
if !ok {
|
||||
matched := false
|
||||
for i, d := range as.computation.Datasets {
|
||||
if hash == d.Hash {
|
||||
if d.Filename != "" && d.Filename != dataset.Filename {
|
||||
return ErrFileNameMismatch
|
||||
}
|
||||
|
||||
as.computation.Datasets = slices.Delete(as.computation.Datasets, i, i+1)
|
||||
|
||||
if DecompressFromContext(ctx) {
|
||||
if err := internal.UnzipFromMemory(dataset.Dataset, algorithm.DatasetsDir); err != nil {
|
||||
return fmt.Errorf("error decompressing dataset: %v", err)
|
||||
}
|
||||
} else {
|
||||
f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, dataset.Filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dataset file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(dataset.Dataset); err != nil {
|
||||
return fmt.Errorf("error writing dataset to file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return ErrUndeclaredDataset
|
||||
}
|
||||
|
||||
if hash != as.computation.Datasets[index].Hash {
|
||||
return ErrHashMismatch
|
||||
}
|
||||
as.computation.Datasets = slices.Delete(as.computation.Datasets, index, index+1)
|
||||
|
||||
f, err := os.CreateTemp("", fmt.Sprintf("dataset-%d", index))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dataset file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(dataset.Dataset); err != nil {
|
||||
return fmt.Errorf("error writing dataset to file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
}
|
||||
|
||||
as.algorithm.AddDataset(f.Name())
|
||||
|
||||
if len(as.computation.Datasets) == 0 {
|
||||
as.sm.SendEvent(dataReceived)
|
||||
}
|
||||
@@ -197,11 +240,11 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
|
||||
}
|
||||
|
||||
func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
if as.sm.GetState() != resultsReady {
|
||||
if as.sm.GetState() != resultsReady && as.sm.GetState() != failed {
|
||||
return []byte{}, ErrResultsNotReady
|
||||
}
|
||||
if len(as.computation.ResultConsumers) == 0 {
|
||||
return []byte{}, ErrAllManifestItemsReceived
|
||||
return []byte{}, ErrAllResultsConsumed
|
||||
}
|
||||
index, ok := IndexFromContext(ctx)
|
||||
if !ok {
|
||||
@@ -209,19 +252,15 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
as.computation.ResultConsumers = slices.Delete(as.computation.ResultConsumers, index, index+1)
|
||||
|
||||
if len(as.computation.ResultConsumers) == 0 {
|
||||
if len(as.computation.ResultConsumers) == 0 && as.sm.GetState() == resultsReady {
|
||||
as.sm.SendEvent(resultsConsumed)
|
||||
}
|
||||
// Return the result file or an error
|
||||
|
||||
return as.result, as.runError
|
||||
}
|
||||
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error) {
|
||||
provider, err := client.GetQuoteProvider()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
rawQuote, err := provider.GetRawQuote(reportData)
|
||||
rawQuote, err := as.quoteProvider.GetRawQuote(reportData)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
@@ -232,17 +271,49 @@ func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataS
|
||||
func (as *agentService) runComputation() {
|
||||
as.publishEvent("starting", json.RawMessage{})()
|
||||
as.sm.logger.Debug("computation run started")
|
||||
defer as.sm.SendEvent(runComplete)
|
||||
as.publishEvent("in-progress", json.RawMessage{})()
|
||||
result, err := as.algorithm.Run()
|
||||
if err != nil {
|
||||
as.runError = err
|
||||
as.sm.logger.Warn(fmt.Sprintf("computation failed with error: %s", err.Error()))
|
||||
defer func() {
|
||||
if as.runError != nil {
|
||||
as.sm.SendEvent(runFailed)
|
||||
} else {
|
||||
as.sm.SendEvent(runComplete)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.Mkdir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
as.runError = fmt.Errorf("error creating results directory: %s", err.Error())
|
||||
as.sm.logger.Warn(as.runError.Error())
|
||||
as.publishEvent("failed", json.RawMessage{})()
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(algorithm.ResultsDir); err != nil {
|
||||
as.sm.logger.Warn(fmt.Sprintf("error removing results directory and its contents: %s", err.Error()))
|
||||
}
|
||||
if err := os.RemoveAll(algorithm.DatasetsDir); err != nil {
|
||||
as.sm.logger.Warn(fmt.Sprintf("error removing datasets directory and its contents: %s", err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
as.publishEvent("in-progress", json.RawMessage{})()
|
||||
if err := as.algorithm.Run(); err != nil {
|
||||
as.runError = err
|
||||
as.sm.logger.Warn(fmt.Sprintf("failed to run computation: %s", err.Error()))
|
||||
as.publishEvent("failed", json.RawMessage{})()
|
||||
return
|
||||
}
|
||||
|
||||
results, err := internal.ZipDirectoryToMemory(algorithm.ResultsDir)
|
||||
if err != nil {
|
||||
as.runError = err
|
||||
as.sm.logger.Warn(fmt.Sprintf("failed to zip results: %s", err.Error()))
|
||||
as.publishEvent("failed", json.RawMessage{})()
|
||||
return
|
||||
}
|
||||
|
||||
as.publishEvent("complete", json.RawMessage{})()
|
||||
as.result = result
|
||||
|
||||
as.result = results
|
||||
}
|
||||
|
||||
func (as *agentService) publishEvent(status string, details json.RawMessage) func() {
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
running
|
||||
resultsReady
|
||||
complete
|
||||
failed
|
||||
)
|
||||
|
||||
type event uint8
|
||||
@@ -31,6 +32,7 @@ const (
|
||||
dataReceived
|
||||
runComplete
|
||||
resultsConsumed
|
||||
runFailed
|
||||
)
|
||||
|
||||
// StateMachine represents the state machine.
|
||||
@@ -74,6 +76,7 @@ func NewStateMachine(logger *slog.Logger, cmp Computation) *StateMachine {
|
||||
|
||||
sm.Transitions[running] = make(map[event]state)
|
||||
sm.Transitions[running][runComplete] = resultsReady
|
||||
sm.Transitions[running][runFailed] = failed
|
||||
|
||||
sm.Transitions[resultsReady] = make(map[event]state)
|
||||
sm.Transitions[resultsReady][resultsConsumed] = complete
|
||||
|
||||
@@ -15,14 +15,15 @@ func _() {
|
||||
_ = x[running-4]
|
||||
_ = x[resultsReady-5]
|
||||
_ = x[complete-6]
|
||||
_ = x[failed-7]
|
||||
}
|
||||
|
||||
const _state_name = "idlereceivingManifestreceivingAlgorithmreceivingDatarunningresultsReadycomplete"
|
||||
const _state_name = "idlereceivingManifestreceivingAlgorithmreceivingDatarunningresultsReadycompletefailed"
|
||||
|
||||
var _state_index = [...]uint8{0, 4, 21, 39, 52, 59, 71, 79}
|
||||
var _state_index = [...]uint8{0, 4, 21, 39, 52, 59, 71, 79, 85}
|
||||
|
||||
func (i state) String() string {
|
||||
if i < 0 || i >= state(len(_state_index)-1) {
|
||||
if i >= state(len(_state_index)-1) {
|
||||
return "state(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _state_name[_state_index[i]:_state_index[i+1]]
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package cocosai
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed attestation.bin
|
||||
var EmbeddedAttestation []byte
|
||||
+26
-6
@@ -16,14 +16,14 @@ make cli
|
||||
Retrieves attestation information from the SEV guest and saves it to a file.
|
||||
To retrieve attestation from agent, use the following command:
|
||||
```bash
|
||||
./build/cocos-cli agent attestation get '<report_data>'
|
||||
./build/cocos-cli attestation get '<report_data>'
|
||||
```
|
||||
|
||||
#### Validate attestation
|
||||
Validates the retrieved attestation information against a specified policy and checks its authenticity.
|
||||
To validate and verify attestation from agent, use the following command:
|
||||
```bash
|
||||
./build/cocos-cli agent attestation validate '<attestation>' --report_data '<report_data>'
|
||||
./build/cocos-cli attestation validate '<attestation>' --report_data '<report_data>'
|
||||
```
|
||||
##### Flags
|
||||
- --config: Path to a JSON file containing the validation configuration. This can be used to override individual flags.
|
||||
@@ -62,21 +62,41 @@ To validate and verify attestation from agent, use the following command:
|
||||
To upload an algorithm, use the following command:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli agent algo /path/to/algorithm <private_key_file_path>
|
||||
./build/cocos-cli algo /path/to/algorithm <private_key_file_path>
|
||||
```
|
||||
|
||||
##### Flags
|
||||
- -a, --algorithm string Algorithm type to run (default "bin")
|
||||
- --args stringArray Arguments to pass to the algorithm
|
||||
- --python-runtime string Python runtime to use (default "python3")
|
||||
- -r, --requirements string Python requirements file
|
||||
|
||||
#### Upload Dataset
|
||||
|
||||
To upload a dataset, use the following command:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli agent data /path/to/dataset.csv <private_key_file_path>
|
||||
./build/cocos-cli data /path/to/dataset.csv <private_key_file_path>
|
||||
```
|
||||
|
||||
Users can also upload directories which will be compressed on transit. Once received by agent they will be stored as compressed files or decompressed if the user passed the decompression argument.
|
||||
|
||||
##### Flags
|
||||
- -d, --decompress Decompress the dataset on agent
|
||||
|
||||
|
||||
|
||||
#### Retrieve result
|
||||
|
||||
To retrieve the computation result, use the following command:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli agent result <private_key_file_path>
|
||||
```
|
||||
./build/cocos-cli result <private_key_file_path>
|
||||
```
|
||||
|
||||
#### Checksum
|
||||
When defining the manifest dataset and algorithm checksums are required. This can be done as below:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli checksum <path_to_dataset_or_algorithm>
|
||||
```
|
||||
|
||||
+4
-1
@@ -19,6 +19,7 @@ var (
|
||||
pythonRuntime string
|
||||
algoType string
|
||||
requirementsFile string
|
||||
algoArgs []string
|
||||
)
|
||||
|
||||
func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
@@ -30,7 +31,7 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
algorithmFile := args[0]
|
||||
|
||||
log.Println("Uploading algorithm binary:", algorithmFile)
|
||||
log.Println("Uploading algorithm file:", algorithmFile)
|
||||
|
||||
algorithm, err := os.ReadFile(algorithmFile)
|
||||
if err != nil {
|
||||
@@ -72,12 +73,14 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
cmd.Flags().StringVarP(&algoType, "algorithm", "a", string(algorithm.AlgoTypeBin), "Algorithm type to run")
|
||||
cmd.Flags().StringVar(&pythonRuntime, "python-runtime", python.PyRuntime, "Python runtime to use")
|
||||
cmd.Flags().StringVarP(&requirementsFile, "requirements", "r", "", "Python requirements file")
|
||||
cmd.Flags().StringArrayVar(&algoArgs, "args", []string{}, "Arguments to pass to the algorithm")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func addAlgoMetadata(ctx context.Context) context.Context {
|
||||
ctx = algorithm.AlgorithmTypeToContext(ctx, algoType)
|
||||
ctx = algorithm.AlgorithmArgsToContext(ctx, algoArgs)
|
||||
ctx = python.PythonRunTimeToContext(ctx, pythonRuntime)
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -3,32 +3,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
)
|
||||
|
||||
func (cli *CLI) NewFileHashCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "file-hash",
|
||||
Use: "checksum",
|
||||
Short: "Compute the sha3-256 hash of a file",
|
||||
Example: "file-hash <file>",
|
||||
Example: "checksum <file>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fileName := args[0]
|
||||
path := args[0]
|
||||
|
||||
file, err := os.ReadFile(fileName)
|
||||
hash, err := internal.ChecksumHex(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading dataset file: %v", err)
|
||||
log.Fatalf("Error computing hash: %v", err)
|
||||
}
|
||||
|
||||
hashBytes := sha3.Sum256(file)
|
||||
|
||||
hash := hex.EncodeToString(hashBytes[:])
|
||||
|
||||
log.Println("Hash of file:", hash)
|
||||
},
|
||||
}
|
||||
+37
-8
@@ -3,33 +3,54 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var decompressDataset bool
|
||||
|
||||
func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "data",
|
||||
Short: "Upload a dataset CSV file",
|
||||
Example: "data <dataset.csv> <private_key_file_path>",
|
||||
Short: "Upload a dataset",
|
||||
Example: "data <dataset_path> <private_key_file_path>",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
datasetFile := args[0]
|
||||
datasetPath := args[0]
|
||||
|
||||
log.Println("Uploading dataset CSV:", datasetFile)
|
||||
log.Println("Uploading dataset:", datasetPath)
|
||||
|
||||
dataset, err := os.ReadFile(datasetFile)
|
||||
f, err := os.Stat(datasetPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading dataset file: %v", err)
|
||||
}
|
||||
|
||||
var dataset []byte
|
||||
|
||||
if f.IsDir() {
|
||||
dataset, err = internal.ZipDirectoryToMemory(datasetPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error zipping dataset directory: %v", err)
|
||||
}
|
||||
} else {
|
||||
dataset, err = os.ReadFile(datasetPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading dataset file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
dataReq := agent.Dataset{
|
||||
Dataset: dataset,
|
||||
Dataset: dataset,
|
||||
Filename: path.Base(datasetPath),
|
||||
}
|
||||
|
||||
privKeyFile, err := os.ReadFile(args[1])
|
||||
@@ -41,13 +62,17 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
|
||||
privKey := decodeKey(pemBlock)
|
||||
|
||||
if err := cli.agentSDK.Data(cmd.Context(), dataReq, privKey); err != nil {
|
||||
ctx := metadata.NewOutgoingContext(cmd.Context(), metadata.New(make(map[string]string)))
|
||||
if err := cli.agentSDK.Data(addDatasetMetadata(ctx), dataReq, privKey); err != nil {
|
||||
log.Fatalf("Error uploading dataset: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Successfully uploaded dataset")
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&decompressDataset, "decompress", "d", false, "Decompress the dataset on agent")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func decodeKey(b *pem.Block) interface{} {
|
||||
@@ -72,3 +97,7 @@ func decodeKey(b *pem.Block) interface{} {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func addDatasetMetadata(ctx context.Context) context.Context {
|
||||
return agent.DecompressToContext(ctx, decompressDataset)
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const resultFilePath = "result.bin"
|
||||
const resultFilePath = "results.zip"
|
||||
|
||||
func (cli *CLI) NewResultsCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
|
||||
+12
-4
@@ -11,12 +11,14 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/mdlayher/vsock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/api"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/agent/quoteprovider"
|
||||
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
|
||||
@@ -62,7 +64,13 @@ func main() {
|
||||
}
|
||||
defer eventSvc.Close()
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, cfg)
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, cfg, qp)
|
||||
|
||||
grpcServerConfig := server.Config{
|
||||
Port: cfg.AgentConfig.Port,
|
||||
@@ -85,7 +93,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, svc, authSvc)
|
||||
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, qp, authSvc)
|
||||
|
||||
g.Go(func() error {
|
||||
return gs.Start()
|
||||
@@ -100,8 +108,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp agent.Computation) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, cmp)
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp agent.Computation, qp client.QuoteProvider) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, cmp, qp)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "api")
|
||||
|
||||
+16
-13
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
"github.com/ultravioletrs/cocos/manager/api"
|
||||
managerapi "github.com/ultravioletrs/cocos/manager/api/grpc"
|
||||
@@ -37,10 +36,11 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MANAGER_LOG_LEVEL" envDefault:"info"`
|
||||
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
|
||||
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
|
||||
LogLevel string `env:"MANAGER_LOG_LEVEL" envDefault:"info"`
|
||||
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
|
||||
TraceRatio float64 `env:"COCOS_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
|
||||
BackendMeasurementBinary string `env:"MANAGER_BACKEND_MEASUREMENT_BINARY" envDefault:"../../build"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -103,7 +103,11 @@ func main() {
|
||||
}
|
||||
|
||||
eventsChan := make(chan *pkgmanager.ClientStreamMessage)
|
||||
svc := newService(logger, tracer, qemuCfg, eventsChan)
|
||||
svc, err := newService(logger, tracer, qemuCfg, eventsChan, cfg.BackendMeasurementBinary)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
mc := managerapi.NewClient(pc, svc, eventsChan)
|
||||
|
||||
@@ -114,19 +118,18 @@ func main() {
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
|
||||
if err = internal.DeleteFilesInDir(qemuCfg.TmpFileLoc); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, eventsChan chan *pkgmanager.ClientStreamMessage) manager.Service {
|
||||
svc := manager.New(qemuCfg, logger, eventsChan, qemu.NewVM)
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, eventsChan chan *pkgmanager.ClientStreamMessage, backendMeasurementPath string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, backendMeasurementPath, logger, eventsChan, qemu.NewVM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go svc.RetrieveAgentEventsLogs()
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "api")
|
||||
svc = api.MetricsMiddleware(svc, counter, latency)
|
||||
svc = tracing.New(svc, tracer)
|
||||
|
||||
return svc
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# user changeable parameters
|
||||
#
|
||||
|
||||
HDA_FILE="cmd/manager/img/focal-server-cloudimg-amd64.qcow2"
|
||||
GUEST_SIZE_IN_MB="4096"
|
||||
SEV_GUEST="1"
|
||||
SMP_NCPUS="4"
|
||||
CONSOLE="serial"
|
||||
VNC_PORT=""
|
||||
USE_VIRTIO="1"
|
||||
|
||||
UEFI_BIOS_CODE="/usr/share/OVMF/OVMF_CODE.fd"
|
||||
UEFI_BIOS_VARS_ORIG="/usr/share/OVMF/OVMF_VARS.fd"
|
||||
UEFI_BIOS_VARS_COPY="cmd/manager/img/OVMF_VARS.fd"
|
||||
|
||||
CBITPOS=51
|
||||
HOST_HTTP_PORT=9301
|
||||
GUEST_HTTP_PORT=9031
|
||||
HOST_GRPC_PORT=7020
|
||||
GUEST_GRPC_PORT=7002
|
||||
|
||||
ENABLE_FILE_LOG="0"
|
||||
EXEC_QEMU_CMDLINE="0"
|
||||
|
||||
usage() {
|
||||
echo "$0 [options]"
|
||||
echo "Available <commands>:"
|
||||
echo " -hda hard disk ($HDA_FILE)"
|
||||
echo " -nosev disable sev support"
|
||||
echo " -mem guest memory"
|
||||
echo " -smp number of cpus"
|
||||
echo " -console display console to use (serial or gxl)"
|
||||
echo " -vnc VNC port to use"
|
||||
echo " -bios bios to use (default $UEFI_BIOS_CODE)"
|
||||
echo " -kernel kernel to use"
|
||||
echo " -initrd initrd to use"
|
||||
echo " -cdrom CDROM image"
|
||||
echo " -virtio use virtio devices"
|
||||
echo " -cbitpos location of the C-bit"
|
||||
echo " -hosthttp host http port"
|
||||
echo " -guesthttp guest http port"
|
||||
echo " -hostgrpc host grpc port"
|
||||
echo " -guestgrpc guest grpc port"
|
||||
echo " -origuefivars UEFI BIOS vars original file (default $UEFI_BIOS_VARS_ORIG)"
|
||||
echo " -copyuefivars UEFI BIOS vars copy file (default $UEFI_BIOS_VARS_COPY)"
|
||||
echo " -exec execute the QEMU command (default $EXEC_QEMU_CMDLINE)"
|
||||
echo " -filelog enable/disable QEMU cmd line file log (default: $ENABLE_FILE_LOG)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $1 != "" ]]; do
|
||||
case "$1" in
|
||||
-hda)
|
||||
HDA_FILE=${2}
|
||||
shift
|
||||
;;
|
||||
-nosev)
|
||||
SEV_GUEST="0"
|
||||
;;
|
||||
-mem)
|
||||
GUEST_SIZE_IN_MB=${2}
|
||||
shift
|
||||
;;
|
||||
-console)
|
||||
CONSOLE=${2}
|
||||
shift
|
||||
;;
|
||||
-smp)
|
||||
SMP_NCPUS=$2
|
||||
shift
|
||||
;;
|
||||
-vnc)
|
||||
VNC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-bios)
|
||||
UEFI_BIOS_CODE=$2
|
||||
shift
|
||||
;;
|
||||
-initrd)
|
||||
INITRD_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-kernel)
|
||||
KERNEL_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-cdrom)
|
||||
CDROM_FILE=$2
|
||||
shift
|
||||
;;
|
||||
-virtio)
|
||||
USE_VIRTIO="1"
|
||||
;;
|
||||
-cbitpos)
|
||||
CBITPOS=$2
|
||||
shift
|
||||
;;
|
||||
-hosthttp)
|
||||
HOST_HTTP_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-guesthttp)
|
||||
GUEST_HTTP_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-guestgrpc)
|
||||
GUEST_GRPC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-hostgrpc)
|
||||
HOST_GRPC_PORT=$2
|
||||
shift
|
||||
;;
|
||||
-origuefivars)
|
||||
UEFI_BIOS_VARS_ORIG=$2
|
||||
shift
|
||||
;;
|
||||
-copyuefivars)
|
||||
UEFI_BIOS_VARS_COPY=$2
|
||||
shift
|
||||
;;
|
||||
-exec)
|
||||
EXEC_QEMU_CMDLINE="1"
|
||||
;;
|
||||
-filelog)
|
||||
ENABLE_FILE_LOG="1"
|
||||
;;
|
||||
*)
|
||||
usage;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
#
|
||||
# func definitions
|
||||
#
|
||||
|
||||
add_opts() {
|
||||
echo -n "$* " >> ${QEMU_CMDLINE}
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
if ! "$@"; then
|
||||
echo "Command '$*' failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# copy BIOS variables to new dest for VM use without modifying the original ones
|
||||
cp "$UEFI_BIOS_VARS_ORIG" "$UEFI_BIOS_VARS_COPY"
|
||||
|
||||
#
|
||||
# Qemu cmd line construction
|
||||
#
|
||||
|
||||
# we add all the qemu command line options into a file
|
||||
QEMU_CMDLINE=/tmp/cmdline.$$
|
||||
rm -rf ${QEMU_CMDLINE}
|
||||
|
||||
add_opts "$(which qemu-system-x86_64)"
|
||||
|
||||
# Basic virtual machine property
|
||||
add_opts "-enable-kvm -cpu EPYC -machine q35"
|
||||
|
||||
# add number of VCPUs
|
||||
[ -n "$SMP_NCPUS" ] && add_opts "-smp ${SMP_NCPUS},maxcpus=64"
|
||||
|
||||
# define guest memory
|
||||
add_opts "-m ${GUEST_SIZE_IN_MB}M,slots=5,maxmem=30G"
|
||||
|
||||
# The OVMF binary, including the non-volatile variable store, appears as a
|
||||
# "normal" qemu drive on the host side, and it is exposed to the guest as a
|
||||
# persistent flash device.
|
||||
add_opts "-drive if=pflash,format=raw,unit=0,file=${UEFI_BIOS_CODE},readonly=on"
|
||||
add_opts "-drive if=pflash,format=raw,unit=1,file=${UEFI_BIOS_VARS_COPY}"
|
||||
|
||||
# add CDROM if specified
|
||||
[ -n "$CDROM_FILE" ] && add_opts "-drive file=${CDROM_FILE},media=cdrom -boot d"
|
||||
|
||||
add_opts "-netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::$HOST_HTTP_PORT-:$GUEST_HTTP_PORT,hostfwd=tcp::$HOST_GRPC_PORT-:$GUEST_GRPC_PORT"
|
||||
add_opts "-device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile="
|
||||
|
||||
# If harddisk file is specified then add the HDD drive
|
||||
if [ -n "$HDA_FILE" ]; then
|
||||
if [ "$USE_VIRTIO" = "1" ]; then
|
||||
if [[ ${HDA_FILE} = *"qcow2" ]]; then
|
||||
add_opts "-drive file=${HDA_FILE},if=none,id=disk0,format=qcow2"
|
||||
else
|
||||
add_opts "-drive file=${HDA_FILE},if=none,id=disk0,format=raw"
|
||||
fi
|
||||
add_opts "-device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true"
|
||||
add_opts "-device scsi-hd,drive=disk0"
|
||||
else
|
||||
if [[ ${HDA_FILE} = *"qcow2" ]]; then
|
||||
add_opts "-drive file=${HDA_FILE},format=qcow2"
|
||||
else
|
||||
add_opts "-drive file=${HDA_FILE},format=raw"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# If this is SEV guest then add the encryption device objects to enable support
|
||||
if [ ${SEV_GUEST} = "1" ]; then
|
||||
add_opts "-object sev-guest,id=sev0,cbitpos=${CBITPOS},reduced-phys-bits=1"
|
||||
add_opts "-machine memory-encryption=sev0"
|
||||
fi
|
||||
|
||||
# if console is serial then disable graphical interface
|
||||
if [ "${CONSOLE}" = "serial" ]; then
|
||||
add_opts "-nographic"
|
||||
else
|
||||
add_opts "-vga ${CONSOLE}"
|
||||
fi
|
||||
|
||||
# if -kernel arg is specified then use the kernel provided in command line for boot
|
||||
if [ "${KERNEL_FILE}" != "" ]; then
|
||||
add_opts "-kernel $KERNEL_FILE"
|
||||
add_opts "-append \"console=ttyS0 earlyprintk=serial root=/dev/sda2\""
|
||||
[ -n "$INITRD_FILE" ] && add_opts "-initrd ${INITRD_FILE}"
|
||||
fi
|
||||
|
||||
# start vnc server
|
||||
[ -n "$VNC_PORT" ] && add_opts "-vnc :${VNC_PORT}" && echo "Starting VNC on port ${VNC_PORT}"
|
||||
|
||||
# start monitor on pty
|
||||
add_opts "-monitor pty"
|
||||
|
||||
#
|
||||
# Qemu cmd line log
|
||||
#
|
||||
|
||||
# Set the log file path if ENABLE_FILE_LOG is 1
|
||||
if [ "$ENABLE_FILE_LOG" = "1" ]; then
|
||||
LOG_FILE=$(pwd)/stdout.log
|
||||
|
||||
# Save the command line args into log file
|
||||
cat "$QEMU_CMDLINE" > "$LOG_FILE"
|
||||
echo >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Log the command line to the console
|
||||
cat "$QEMU_CMDLINE"
|
||||
|
||||
#
|
||||
# Qemu cmd line execution
|
||||
#
|
||||
|
||||
if [[ "${EXEC_QEMU_CMDLINE}" = "0" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# map CTRL-C to CTRL ]
|
||||
echo "Mapping CTRL-C to CTRL-]"
|
||||
stty intr ^]
|
||||
|
||||
echo "Launching VM ..."
|
||||
if [ "$ENABLE_FILE_LOG" = "1" ]; then
|
||||
bash ${QEMU_CMDLINE} 2>&1 | tee -a "${LOG_FILE}"
|
||||
else
|
||||
bash ${QEMU_CMDLINE} 2>&1
|
||||
fi
|
||||
|
||||
# restore the mapping
|
||||
stty intr ^c
|
||||
|
||||
rm -rf ${QEMU_CMDLINE}
|
||||
@@ -23,16 +23,29 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/docker v27.1.0+incompatible
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
@@ -55,7 +68,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/term v0.22.0
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/absmach/magistrala v0.14.1-0.20240709113739-04c359462746 h1:Tj567KeGVygjTsSCxn4++skKiz9GkPugM1KMdIFxvfw=
|
||||
@@ -15,6 +17,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752 h1:NI7XEcHzWVvBfVjSVK6Qk4wmrUfoyQxCNpBjrHelZFk=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752/go.mod h1:/Ok8PA2qi/ve0Py38+oL+VxoYmlowigYRyLEODRYdgc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
|
||||
github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
@@ -28,6 +40,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -45,6 +59,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -53,6 +69,12 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
|
||||
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -78,8 +100,12 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
@@ -96,19 +122,50 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/agent/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/wasmtime/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/wasmedge/Config.in"
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
# Hardware Abstraction Layer (HAL) for Confidential Computing
|
||||
Cocos HAL for Linux is framework for building custom in-enclave Linux distribution.
|
||||
|
||||
Cocos HAL for Linux is framework for building custom in-enclave Linux distribution.
|
||||
|
||||
## Usage
|
||||
|
||||
HAL uses [Buildroot](https://buildroot.org/)'s [_External Tree_ mechanism](https://buildroot.org/downloads/manual/manual.html#outside-br-custom) for building custom distro:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -40,6 +40,9 @@ CONFIG_TMPFS_POSIX_ACL=y
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VIRTIO_VSOCKETS=y
|
||||
CONFIG_NF_TABLES=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_CGROUP_BPF=y
|
||||
|
||||
###
|
||||
# AMD SEV-SNP
|
||||
@@ -61,4 +64,4 @@ CONFIG_PREEMPT=n
|
||||
CONFIG_PREEMPT_DYNAMIC=n
|
||||
CONFIG_DEBUG_PREEMPT=n
|
||||
CONFIG_CGROUP_MISC=y
|
||||
CONFIG_X86_CPUID=y
|
||||
CONFIG_X86_CPUID=y
|
||||
|
||||
@@ -45,13 +45,13 @@ BR2_PACKAGE_HOST_QEMU=y
|
||||
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
|
||||
|
||||
# Docker
|
||||
BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS=y
|
||||
BR2_PACKAGE_LIBSECCOMP=y
|
||||
BR2_PACKAGE_CA_CERTIFICATES=y
|
||||
BR2_PACKAGE_DOCKER_CLI=y
|
||||
BR2_PACKAGE_DOCKER_COMPOSE=y
|
||||
BR2_PACKAGE_DOCKER_ENGINE=y
|
||||
BR2_PACKAGE_CONTAINERD=y
|
||||
BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS=y
|
||||
BR2_PACKAGE_LIBSECCOMP=y
|
||||
BR2_PACKAGE_CA_CERTIFICATES=y
|
||||
BR2_PACKAGE_DOCKER_CLI=y
|
||||
BR2_PACKAGE_DOCKER_COMPOSE=y
|
||||
BR2_PACKAGE_DOCKER_ENGINE=y
|
||||
BR2_PACKAGE_CONTAINERD=y
|
||||
BR2_PACKAGE_RUNC=y
|
||||
BR2_PACKAGE_IPTABLES=y
|
||||
|
||||
@@ -61,7 +61,7 @@ BR2_PACKAGE_PYTHON_PIP=y
|
||||
BR2_PACKAGE_BZIP2=y
|
||||
BR2_PACKAGE_XZ=y
|
||||
BR2_PACKAGE_ZIP=y
|
||||
BR2_PACKAGE_PYTHON3_ZLIB=y
|
||||
BR2_PACKAGE_PYTHON3_ZLIB=y
|
||||
BR2_PACKAGE_PYTHON3_XZ=y
|
||||
BR2_PACKAGE_PYTHON3_BZIP2=y
|
||||
BR2_INSTALL_LIBSTDCPP=y
|
||||
|
||||
@@ -1 +1 @@
|
||||
include $(sort $(wildcard $(BR2_EXTERNAL_COCOS_PATH)/package/*/*.mk))
|
||||
include $(sort $(wildcard $(BR2_EXTERNAL_COCOS_PATH)/package/*/*.mk))
|
||||
|
||||
@@ -3,6 +3,6 @@ config BR2_PACKAGE_AGENT
|
||||
default y
|
||||
help
|
||||
Confidential Computing Agent is a state machine capable of
|
||||
receiving datasets and algorithm, running computations, and
|
||||
fetching the attestation report from within the
|
||||
Confidential VM.
|
||||
receiving datasets and algorithm, running computations, and
|
||||
fetching the attestation report from within the
|
||||
Confidential VM.
|
||||
|
||||
@@ -7,20 +7,21 @@
|
||||
AGENT_VERSION = main
|
||||
AGENT_SITE = $(call github,ultravioletrs,cocos,$(AGENT_VERSION))
|
||||
|
||||
define AGENT_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) agent
|
||||
define AGENT_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) agent EMBED_ENABLED=$(AGENT_EMBED_ENABLED)
|
||||
endef
|
||||
|
||||
define AGENT_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
|
||||
mkdir -p $(TARGET_DIR)/var/log/cocos
|
||||
mkdir -p $(TARGET_DIR)/cocos/
|
||||
mkdir -p $(TARGET_DIR)/var/log/cocos
|
||||
mkdir -p $(TARGET_DIR)/cocos_init/
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
|
||||
endef
|
||||
|
||||
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 0750 $(@D)/init/systemd/cocos_network_setup.sh $(TARGET_DIR)/cocos/cocos_network_setup.sh
|
||||
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos/agent_start_script.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
|
||||
endef
|
||||
|
||||
$(eval $(golang-package))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
config BR2_PACKAGE_WASMEDGE
|
||||
bool "wasmedge"
|
||||
default y
|
||||
help
|
||||
Wasmedge is a standalone runtime for WebAssembly.
|
||||
https://wasmedge.org/docs/
|
||||
@@ -0,0 +1,8 @@
|
||||
WASMEDGE_DOWNLOAD_URL = https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh
|
||||
|
||||
define WASMEDGE_INSTALL_TARGET_CMDS
|
||||
curl -sSf $(WASMEDGE_DOWNLOAD_URL) | bash -s -- -p $(TARGET_DIR)/usr
|
||||
echo "source /usr/env" >> $(TARGET_DIR)/etc/profile
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -1,5 +0,0 @@
|
||||
config BR2_PACKAGE_WASMTIME
|
||||
bool "wasmtime"
|
||||
help
|
||||
Wasmtime is a standalone runtime for WebAssembly.
|
||||
https://github.com/bytecodealliance/wasmtime
|
||||
@@ -1,11 +0,0 @@
|
||||
WASMTIME_SITE = https://wasmtime.dev/install.sh
|
||||
|
||||
define WASMTIME_BUILD_CMDS
|
||||
curl $(WASMTIME_SITE) -sSf | bash
|
||||
endef
|
||||
|
||||
define WASMTIME_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0755 ~/.wasmtime/bin/wasmtime $(TARGET_DIR)/usr/bin/wasmtime
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
MOUNT_POINT="/cocos"
|
||||
|
||||
# IFACES are all network interfaces excluding lo (LOOPBACK) and sit interfaces
|
||||
IFACES=$(ip link show | grep -vE 'LOOPBACK|sit*' | awk -F': ' '{print $2}')
|
||||
|
||||
@@ -15,3 +17,9 @@ for IFACE in $IFACES; do
|
||||
dhclient $IFACE
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "$MOUNT_POINT" ]; then
|
||||
mkdir -p $MOUNT_POINT
|
||||
fi
|
||||
|
||||
mount -t ramfs ramfs $MOUNT_POINT
|
||||
@@ -1,5 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Change the docker.service file to allow the Docker to run in RAM
|
||||
mkdir -p /etc/systemd/system/docker.service.d
|
||||
|
||||
# Create or overwrite the override.conf file with the new Environment variable
|
||||
tee /etc/systemd/system/docker.service.d/override.conf > /dev/null <<EOF
|
||||
[Service]
|
||||
Environment=DOCKER_RAMDISK=true
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
NUM_OF_PERMITED_IFACE=1
|
||||
|
||||
NUM_OF_IFACE=$(ip route | grep -Eo 'dev [a-z0-9]+' | awk '{ print $2 }' | sort | uniq | wc -l)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
[Unit]
|
||||
Description=Cocos AI agent
|
||||
After=network.target
|
||||
Before=docker.service
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/cocos
|
||||
StandardOutput=file:/var/log/cocos/agent.stdout
|
||||
StandardError=file:/var/log/cocos/agent.stderr
|
||||
|
||||
Environment=AGENT_GRPC_PORT=7002
|
||||
Environment=AGENT_LOG_LEVEL=info
|
||||
|
||||
ExecStartPre=/cocos/cocos_network_setup.sh
|
||||
ExecStart=/cocos/agent_start_script.sh
|
||||
ExecStartPre=/cocos_init/agent_setup.sh
|
||||
ExecStart=/cocos_init/agent_start_script.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// CopyFile copies a file from srcPath to dstPath.
|
||||
@@ -46,3 +49,37 @@ func DeleteFilesInDir(dirPath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checksum calculates the SHA3-256 checksum of the file or directory at path.
|
||||
func Checksum(path string) ([]byte, error) {
|
||||
file, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if file.IsDir() {
|
||||
f, err := ZipDirectoryToMemory(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sum := sha3.Sum256(f)
|
||||
return sum[:], nil
|
||||
} else {
|
||||
f, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sum := sha3.Sum256(f)
|
||||
return sum[:], nil
|
||||
}
|
||||
}
|
||||
|
||||
// ChecksumHex calculates the SHA3-256 checksum of the file or directory at path and returns it as a hex-encoded string.
|
||||
func ChecksumHex(path string) (string, error) {
|
||||
sum, err := Checksum(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(sum), nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
@@ -48,7 +48,7 @@ type Server struct {
|
||||
server.BaseServer
|
||||
server *grpc.Server
|
||||
registerService serviceRegister
|
||||
agent agent.Service
|
||||
quoteProvider client.QuoteProvider
|
||||
authSvc auth.Authenticator
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ type serviceRegister func(srv *grpc.Server)
|
||||
|
||||
var _ server.Server = (*Server)(nil)
|
||||
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger, agentSvc agent.Service, authSvc auth.Authenticator) server.Server {
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger, qp client.QuoteProvider, authSvc auth.Authenticator) server.Server {
|
||||
listenFullAddress := fmt.Sprintf("%s:%s", config.Host, config.Port)
|
||||
return &Server{
|
||||
BaseServer: server.BaseServer{
|
||||
@@ -68,7 +68,7 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
|
||||
Logger: logger,
|
||||
},
|
||||
registerService: registerService,
|
||||
agent: agentSvc,
|
||||
quoteProvider: qp,
|
||||
authSvc: authSvc,
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (s *Server) Start() error {
|
||||
|
||||
switch {
|
||||
case s.Config.AttestedTLS:
|
||||
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.agent)
|
||||
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.quoteProvider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
@@ -228,7 +228,7 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
|
||||
return tls.X509KeyPair(cert, key)
|
||||
}
|
||||
|
||||
func generateCertificatesForATLS(svc agent.Service) ([]byte, []byte, error) {
|
||||
func generateCertificatesForATLS(qp client.QuoteProvider) ([]byte, []byte, error) {
|
||||
curve := elliptic.P256()
|
||||
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
@@ -241,7 +241,7 @@ func generateCertificatesForATLS(svc agent.Service) ([]byte, []byte, error) {
|
||||
}
|
||||
|
||||
// The Attestation Report will be added as an X.509 certificate extension
|
||||
attestationReport, err := svc.Attestation(context.Background(), sha3.Sum512(publicKeyBytes))
|
||||
attestationReport, err := qp.GetRawQuote(sha3.Sum512(publicKeyBytes))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to fetch the attestation report: %w", err)
|
||||
}
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package internal
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ZipDirectoryToMemory(sourceDir string) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
zipWriter := zip.NewWriter(buf)
|
||||
|
||||
err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zipHeader, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zipHeader.Name = relPath
|
||||
|
||||
zipWriterEntry, err := zipWriter.CreateHeader(zipHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileToZip, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileToZip.Close()
|
||||
|
||||
_, err = io.Copy(zipWriterEntry, fileToZip)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
zipWriter.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := zipWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func UnzipFromMemory(zipData []byte, targetDir string) error {
|
||||
reader := bytes.NewReader(zipData)
|
||||
zipReader, err := zip.NewReader(reader, int64(len(zipData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range zipReader.File {
|
||||
filePath := filepath.Join(targetDir, file.Name)
|
||||
|
||||
if file.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcFile, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+55
-12
@@ -6,17 +6,60 @@ Manager service provides a barebones gRPC API and Service interface implementati
|
||||
|
||||
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------------- | -------------------------------------------------------- | --------------------------------- |
|
||||
| MANAGER_LOG_LEVEL | Log level for manager service (debug, info, warn, error) | info |
|
||||
| MANAGER_GRPC_HOST | Manager service gRPC host | |
|
||||
| MANAGER_GRPC_PORT | Manager service gRPC port | 7001 |
|
||||
| MANAGER_GRPC_SERVER_CERT | Path to server certificate in pem format | |
|
||||
| MANAGER_GRPC_SERVER_KEY | Path to server key in pem format | |
|
||||
| MANAGER_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | |
|
||||
| MANAGER_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | |
|
||||
| COCOS_JAEGER_URL | Jaeger server URL | http://localhost:4318 |
|
||||
| MANAGER_INSTANCE_ID | Manager service instance ID | |
|
||||
| Variable | Description | Default |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
|
||||
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
|
||||
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
|
||||
| MANAGER_BACKEND_MEASUREMENT_BINARY | The file path for the backend measurement binary. | ../../build |
|
||||
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
|
||||
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
|
||||
| MANAGER_GRPC_SERVER_CA_CERTS | The file path for the server CA certificate(s). | |
|
||||
| MANAGER_GRPC_URL | The URL for the gRPC endpoint. | localhost:7001 |
|
||||
| MANAGER_GRPC_TIMEOUT | The timeout for gRPC requests. | 60s |
|
||||
| MANAGER_INSTANCE_ID | Manager service instance ID | |
|
||||
| MANAGER_QEMU_MEMORY_SIZE | The total memory size for the virtual machine. Can be specified in a human-readable format like "2048M" or "4G". | 2048M |
|
||||
| MANAGER_QEMU_MEMORY_SLOTS | The number of memory slots for the virtual machine. | 5 |
|
||||
| MANAGER_QEMU_MAX_MEMORY | The maximum memory size for the virtual machine. Can be specified in a human-readable format like "30G". | 30G |
|
||||
| MANAGER_QEMU_OVMF_CODE_IF | The interface type for the OVMF code. | pflash |
|
||||
| MANAGER_QEMU_OVMF_CODE_FORMAT | The format of the OVMF code file. | raw |
|
||||
| MANAGER_QEMU_OVMF_CODE_UNIT | The unit number for the OVMF code. | 0 |
|
||||
| MANAGER_QEMU_OVMF_CODE_FILE | The file path for the OVMF code. | /usr/share/OVMF/OVMF_CODE.fd |
|
||||
| MANAGER_QEMU_OVMF_CODE_READONLY | Whether the OVMF code should be read-only. | on |
|
||||
| MANAGER_QEMU_OVMF_VARS_IF | The interface type for the OVMF variables. | pflash |
|
||||
| MANAGER_QEMU_OVMF_VARS_FORMAT | The format of the OVMF variables file. | raw |
|
||||
| MANAGER_QEMU_OVMF_VARS_UNIT | The unit number for the OVMF variables. | 1 |
|
||||
| MANAGER_QEMU_OVMF_VARS_FILE | The file path for the OVMF variables. | /usr/share/OVMF/OVMF_VARS.fd |
|
||||
| MANAGER_QEMU_NETDEV_ID | The ID for the network device. | vmnic |
|
||||
| MANAGER_QEMU_HOST_FWD_AGENT | The port number for the host forward agent. | 7020 |
|
||||
| MANAGER_QEMU_GUEST_FWD_AGENT | The port number for the guest forward agent. | 7002 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY | Whether to disable the legacy PCI device. | on |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM | Whether to enable the IOMMU platform for the virtio-net PCI device. | true |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ADDR | The PCI address for the virtio-net PCI device. | 0x2 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
|
||||
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
|
||||
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
|
||||
| MANAGER_QEMU_SEV_ID | The ID for the Secure Encrypted Virtualization (SEV) device. | sev0 |
|
||||
| MANAGER_QEMU_SEV_CBITPOS | The position of the C-bit in the physical address. | 51 |
|
||||
| MANAGER_QEMU_SEV_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV. | 1 |
|
||||
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV host. | |
|
||||
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
|
||||
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
|
||||
| MANAGER_QEMU_VSOCK_VNC | Whether to enable the virtual socket device for VNC. | 0 |
|
||||
| MANAGER_QEMU_BIN_PATH | The file path for the QEMU binary. | qemu-system-x86_64 |
|
||||
| MANAGER_QEMU_USE_SUDO | Whether to use sudo to run QEMU. | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV | Whether to enable Secure Encrypted Virtualization (SEV). | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV_SNP | Whether to enable Secure Nested Paging (SEV-SNP). | true |
|
||||
| MANAGER_QEMU_ENABLE_KVM | Whether to enable the Kernel-based Virtual Machine (KVM) acceleration. | true |
|
||||
| MANAGER_QEMU_MACHINE | The machine type for QEMU. | q35 |
|
||||
| MANAGER_QEMU_CPU | The CPU model for QEMU. | EPYC |
|
||||
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
|
||||
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
|
||||
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
|
||||
| MANAGER_QEMU_KERNEL_HASH | Whether to enable kernel hash verification. | false |
|
||||
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
|
||||
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
|
||||
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -41,7 +84,7 @@ sudo apt update
|
||||
sudo apt install qemu-kvm
|
||||
```
|
||||
|
||||
Create `img` directory in `cmd/manager`. Create `tmp` directory in `cmd/manager`.
|
||||
Create `img` directory in `cmd/manager`.
|
||||
|
||||
#### Add Vsock
|
||||
The necessary kernel modules must be loaded on the hypervisor. To check if `vhost_vsock` is loaded run:
|
||||
|
||||
+53
-13
@@ -3,15 +3,20 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var errTerminationFromServer = errors.New("server requested client termination")
|
||||
var (
|
||||
errTerminationFromServer = errors.New("server requested client termination")
|
||||
errCorruptedManifest = errors.New("received manifest may be corrupted")
|
||||
)
|
||||
|
||||
type ManagerClient struct {
|
||||
stream pkgmanager.ManagerService_ProcessClient
|
||||
@@ -32,26 +37,61 @@ func (client ManagerClient) Process(ctx context.Context, cancel context.CancelFu
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
var runReqBuffer bytes.Buffer
|
||||
for {
|
||||
req, err := client.stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch mes := req.Message.(type) {
|
||||
case *pkgmanager.ServerStreamMessage_RunReq:
|
||||
port, err := client.svc.Run(ctx, mes.RunReq)
|
||||
case *pkgmanager.ServerStreamMessage_RunReqChunks:
|
||||
if len(mes.RunReqChunks.Data) == 0 {
|
||||
var runReq pkgmanager.ComputationRunReq
|
||||
if err = proto.Unmarshal(runReqBuffer.Bytes(), &runReq); err != nil {
|
||||
return errors.Wrap(err, errCorruptedManifest)
|
||||
}
|
||||
port, err := client.svc.Run(ctx, &runReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runRes := &pkgmanager.ClientStreamMessage_RunRes{
|
||||
RunRes: &pkgmanager.RunResponse{
|
||||
AgentPort: port,
|
||||
ComputationId: runReq.Id,
|
||||
},
|
||||
}
|
||||
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: runRes}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := runReqBuffer.Write(mes.RunReqChunks.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *pkgmanager.ServerStreamMessage_TerminateReq:
|
||||
cancel()
|
||||
return errors.Wrap(errTerminationFromServer, errors.New(mes.TerminateReq.Message))
|
||||
case *pkgmanager.ServerStreamMessage_StopComputation:
|
||||
msg := &pkgmanager.ClientStreamMessage_StopComputationRes{StopComputationRes: &pkgmanager.StopComputationResponse{
|
||||
ComputationId: mes.StopComputation.ComputationId,
|
||||
}}
|
||||
if err := client.svc.Stop(ctx, mes.StopComputation.ComputationId); err != nil {
|
||||
msg.StopComputationRes.Message = err.Error()
|
||||
}
|
||||
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: msg}); err != nil {
|
||||
return err
|
||||
}
|
||||
case *pkgmanager.ServerStreamMessage_BackendInfoReq:
|
||||
res, err := client.svc.FetchBackendInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runRes := &pkgmanager.ClientStreamMessage_RunRes{RunRes: &pkgmanager.RunResponse{AgentPort: port, ComputationId: mes.RunReq.Id}}
|
||||
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: runRes}); err != nil {
|
||||
return err
|
||||
}
|
||||
case *pkgmanager.ServerStreamMessage_TerminateReq:
|
||||
cancel()
|
||||
return errors.Join(errTerminationFromServer, errors.New(mes.TerminateReq.Message))
|
||||
case *pkgmanager.ServerStreamMessage_StopComputation:
|
||||
if err := client.svc.Stop(ctx, mes.StopComputation.ComputationId); err != nil {
|
||||
info := &pkgmanager.ClientStreamMessage_BackendInfo{BackendInfo: &pkgmanager.BackendInfo{
|
||||
Info: res,
|
||||
Id: mes.BackendInfoReq.Id,
|
||||
}}
|
||||
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: info}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,23 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var _ manager.ManagerServiceServer = (*grpcServer)(nil)
|
||||
var (
|
||||
_ manager.ManagerServiceServer = (*grpcServer)(nil)
|
||||
ErrUnexpectedMsg = errors.New("unknown message type")
|
||||
)
|
||||
|
||||
const bufferSize = 1024 * 1024 // 1 MB
|
||||
|
||||
type grpcServer struct {
|
||||
manager.UnimplementedManagerServiceServer
|
||||
@@ -54,8 +64,37 @@ func (s *grpcServer) Process(stream manager.ManagerService_ProcessServer) error
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case req := <-runReqChan:
|
||||
if err := stream.Send(req); err != nil {
|
||||
return err
|
||||
switch msg := req.Message.(type) {
|
||||
case *manager.ServerStreamMessage_RunReq:
|
||||
data, err := proto.Marshal(msg.RunReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataBuffer := bytes.NewBuffer(data)
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
n, err := dataBuffer.Read(buf)
|
||||
chunk := &manager.ServerStreamMessage{
|
||||
Message: &manager.ServerStreamMessage_RunReqChunks{
|
||||
RunReqChunks: &manager.RunReqChunks{
|
||||
Data: buf[:n],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := stream.Send(chunk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if err := stream.Send(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,3 +57,12 @@ func (lm *loggingMiddleware) Stop(ctx context.Context, computationID string) (er
|
||||
func (lm *loggingMiddleware) RetrieveAgentEventsLogs() {
|
||||
lm.svc.RetrieveAgentEventsLogs()
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) FetchBackendInfo() ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method FetchBackendInfo took %s to complete", time.Since(begin))
|
||||
lm.logger.Info(message)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.FetchBackendInfo()
|
||||
}
|
||||
|
||||
@@ -54,3 +54,12 @@ func (ms *metricsMiddleware) Stop(ctx context.Context, computationID string) err
|
||||
func (ms *metricsMiddleware) RetrieveAgentEventsLogs() {
|
||||
ms.svc.RetrieveAgentEventsLogs()
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) FetchBackendInfo() ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "FetchBackendInfo").Add(1)
|
||||
ms.latency.With("method", "FetchBackendInfo").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.FetchBackendInfo()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !embed
|
||||
// +build !embed
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func (ms *managerService) FetchBackendInfo() ([]byte, error) {
|
||||
cmd := exec.Command("sudo", fmt.Sprintf("%s/backend_info", ms.backendMeasurementBinaryPath), "--policy", "1966081")
|
||||
|
||||
_, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.ReadFile("./backend_info.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package manager
|
||||
|
||||
import backendinfo "github.com/ultravioletrs/cocos/scripts/backend_info"
|
||||
|
||||
func (ms *managerService) FetchBackendInfo() ([]byte, error) {
|
||||
return backendinfo.BackendInfo, nil
|
||||
}
|
||||
+26
-3
@@ -21,11 +21,21 @@ message StopComputation {
|
||||
string computation_id = 1;
|
||||
}
|
||||
|
||||
message StopComputationResponse {
|
||||
string computation_id = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message RunResponse{
|
||||
string agent_port = 1;
|
||||
string computation_id = 2;
|
||||
}
|
||||
|
||||
message BackendInfo{
|
||||
bytes info = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message AgentEvent {
|
||||
string event_type = 1;
|
||||
google.protobuf.Timestamp timestamp = 2;
|
||||
@@ -47,17 +57,25 @@ message ClientStreamMessage {
|
||||
AgentLog agent_log = 1;
|
||||
AgentEvent agent_event = 2;
|
||||
RunResponse run_res = 3;
|
||||
BackendInfo backendInfo = 4;
|
||||
StopComputationResponse stopComputationRes = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message ServerStreamMessage {
|
||||
oneof message {
|
||||
ComputationRunReq runReq = 1;
|
||||
Terminate terminateReq = 2;
|
||||
StopComputation stopComputation = 3;
|
||||
RunReqChunks runReqChunks = 1;
|
||||
ComputationRunReq runReq = 2;
|
||||
Terminate terminateReq = 3;
|
||||
StopComputation stopComputation = 4;
|
||||
BackendInfoReq backendInfoReq = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message RunReqChunks {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message ComputationRunReq {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
@@ -68,6 +86,10 @@ message ComputationRunReq {
|
||||
AgentConfig agent_config = 7;
|
||||
}
|
||||
|
||||
message BackendInfoReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message ResultConsumer {
|
||||
bytes userKey = 1;
|
||||
}
|
||||
@@ -75,6 +97,7 @@ message ResultConsumer {
|
||||
message Dataset {
|
||||
bytes hash = 1; // should be sha3.Sum256, 32 byte length.
|
||||
bytes userKey = 2;
|
||||
string filename = 3;
|
||||
}
|
||||
|
||||
message Algorithm {
|
||||
|
||||
+50
-214
@@ -1,232 +1,68 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package manager
|
||||
package manager_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
"github.com/ultravioletrs/cocos/manager/vm/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
qemuCfg := qemu.Config{}
|
||||
logger := slog.Default()
|
||||
eventsChan := make(chan *manager.ClientStreamMessage)
|
||||
vmf := new(mocks.Provider)
|
||||
func TestProcess(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dial bufnet: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
service := New(qemuCfg, logger, eventsChan, vmf.Execute)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
assert.IsType(t, &managerService{}, service)
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
vmf := new(mocks.Provider)
|
||||
vmMock := new(mocks.VM)
|
||||
vmf.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return(vmMock)
|
||||
tests := []struct {
|
||||
name string
|
||||
req *manager.ComputationRunReq
|
||||
vmStartError error
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Successful run",
|
||||
req: &manager.ComputationRunReq{
|
||||
Id: "test-computation",
|
||||
Name: "Test Computation",
|
||||
Algorithm: &manager.Algorithm{
|
||||
Hash: make([]byte, hashLength),
|
||||
},
|
||||
AgentConfig: &manager.AgentConfig{},
|
||||
},
|
||||
vmStartError: nil,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "VM start failure",
|
||||
req: &manager.ComputationRunReq{
|
||||
Id: "test-computation",
|
||||
Name: "Test Computation",
|
||||
Algorithm: &manager.Algorithm{
|
||||
Hash: make([]byte, hashLength),
|
||||
},
|
||||
AgentConfig: &manager.AgentConfig{},
|
||||
},
|
||||
vmStartError: assert.AnError,
|
||||
expectedError: assert.AnError,
|
||||
},
|
||||
client := manager.NewManagerServiceClient(conn)
|
||||
stream, err := client.Process(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Process failed: %v", err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.vmStartError == nil {
|
||||
vmMock.On("Start").Return(nil).Once()
|
||||
} else {
|
||||
vmMock.On("Start").Return(tt.vmStartError).Once()
|
||||
var data bytes.Buffer
|
||||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to receive ServerStreamMessage: %v", err)
|
||||
}
|
||||
|
||||
switch m := msg.Message.(type) {
|
||||
case *manager.ServerStreamMessage_TerminateReq:
|
||||
if m.TerminateReq.Message != "test terminate" {
|
||||
t.Fatalf("Unexpected terminate message: %v", m.TerminateReq.Message)
|
||||
}
|
||||
case *manager.ServerStreamMessage_RunReqChunks:
|
||||
if len(m.RunReqChunks.Data) == 0 {
|
||||
var runReq manager.ComputationRunReq
|
||||
if err = proto.Unmarshal(data.Bytes(), &runReq); err != nil {
|
||||
t.Fatalf("Failed to create run request: %v", err)
|
||||
}
|
||||
|
||||
vmMock.On("SendAgentConfig", mock.Anything).Return(nil)
|
||||
|
||||
qemuCfg := qemu.Config{
|
||||
VSockConfig: qemu.VSockConfig{
|
||||
GuestCID: 3,
|
||||
Vnc: 5900,
|
||||
},
|
||||
runRes := &manager.ClientStreamMessage_AgentLog{
|
||||
AgentLog: &manager.AgentLog{
|
||||
Message: "test log",
|
||||
ComputationId: "comp1",
|
||||
Level: "DEBUG",
|
||||
},
|
||||
}
|
||||
if runReq.Id != "1" || runReq.Name != "sample computation" || runReq.Description != "sample description" {
|
||||
t.Fatalf("Unexpected run request message: %v", &runReq)
|
||||
}
|
||||
if err := stream.Send(&manager.ClientStreamMessage{Message: runRes}); err != nil {
|
||||
t.Fatalf("Failed to send ClientStreamMessage: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
logger := slog.Default()
|
||||
eventsChan := make(chan *manager.ClientStreamMessage, 10)
|
||||
|
||||
ms := &managerService{
|
||||
qemuCfg: qemuCfg,
|
||||
logger: logger,
|
||||
agents: make(map[int]string),
|
||||
vms: make(map[string]vm.VM),
|
||||
eventsChan: eventsChan,
|
||||
vmFactory: vmf.Execute,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
port, err := ms.Run(ctx, tt.req)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, tt.expectedError)
|
||||
assert.Empty(t, port)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, port)
|
||||
assert.Len(t, ms.vms, 1)
|
||||
assert.Len(t, ms.agents, 1)
|
||||
}
|
||||
|
||||
vmf.AssertExpectations(t)
|
||||
|
||||
// Clear the events channel
|
||||
for len(eventsChan) > 0 {
|
||||
<-eventsChan
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStop(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
computationID string
|
||||
vmStopError error
|
||||
expectedError error
|
||||
initialVMCount int
|
||||
}{
|
||||
{
|
||||
name: "Successful stop",
|
||||
computationID: "existing-computation",
|
||||
vmStopError: nil,
|
||||
expectedError: nil,
|
||||
initialVMCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Non-existent computation",
|
||||
computationID: "non-existent-computation",
|
||||
vmStopError: nil,
|
||||
expectedError: ErrNotFound,
|
||||
initialVMCount: 0,
|
||||
},
|
||||
{
|
||||
name: "VM stop error",
|
||||
computationID: "error-computation",
|
||||
vmStopError: assert.AnError,
|
||||
expectedError: assert.AnError,
|
||||
initialVMCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ms := &managerService{
|
||||
vms: make(map[string]vm.VM),
|
||||
}
|
||||
vmMock := new(mocks.VM)
|
||||
|
||||
if tt.vmStopError == nil {
|
||||
vmMock.On("Stop").Return(nil).Once()
|
||||
} else {
|
||||
vmMock.On("Stop").Return(assert.AnError).Once()
|
||||
}
|
||||
|
||||
if tt.initialVMCount > 0 {
|
||||
ms.vms[tt.computationID] = vmMock
|
||||
}
|
||||
|
||||
err := ms.Stop(context.Background(), tt.computationID)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, ms.vms, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFreePort(t *testing.T) {
|
||||
port, err := getFreePort()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, port, 0)
|
||||
}
|
||||
|
||||
func TestPublishEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
event string
|
||||
computationID string
|
||||
status string
|
||||
details json.RawMessage
|
||||
}{
|
||||
{
|
||||
name: "Standard event",
|
||||
event: "test-event",
|
||||
computationID: "test-computation",
|
||||
status: "test-status",
|
||||
details: nil,
|
||||
},
|
||||
{
|
||||
name: "Event with details",
|
||||
event: "detailed-event",
|
||||
computationID: "detailed-computation",
|
||||
status: "detailed-status",
|
||||
details: json.RawMessage(`{"key": "value"}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eventsChan := make(chan *manager.ClientStreamMessage, 1)
|
||||
ms := &managerService{
|
||||
eventsChan: eventsChan,
|
||||
}
|
||||
|
||||
ms.publishEvent(tt.event, tt.computationID, tt.status, tt.details)
|
||||
|
||||
assert.Len(t, eventsChan, 1)
|
||||
event := <-eventsChan
|
||||
assert.Equal(t, tt.event, event.GetAgentEvent().EventType)
|
||||
assert.Equal(t, tt.computationID, event.GetAgentEvent().ComputationId)
|
||||
assert.Equal(t, tt.status, event.GetAgentEvent().Status)
|
||||
assert.Equal(t, "manager", event.GetAgentEvent().Originator)
|
||||
assert.Equal(t, tt.details, json.RawMessage(event.GetAgentEvent().Details))
|
||||
})
|
||||
data.Write(m.RunReqChunks.Data)
|
||||
default:
|
||||
t.Fatalf("Unexpected message type: %T", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ type VSockConfig struct {
|
||||
|
||||
type Config struct {
|
||||
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
|
||||
TmpFileLoc string `env:"TMP_FILE_LOC" envDefault:"tmp"`
|
||||
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
|
||||
EnableSEV bool `env:"ENABLE_SEV" envDefault:"false"`
|
||||
EnableSEVSNP bool `env:"ENABLE_SEV_SNP" envDefault:"true"`
|
||||
@@ -99,6 +98,9 @@ type Config struct {
|
||||
// display
|
||||
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
|
||||
Monitor string `env:"MONITOR" envDefault:"pty"`
|
||||
|
||||
// ports
|
||||
HostFwdRange string `env:"HOST_FWD_RANGE" envDefault:"6100-6200"`
|
||||
}
|
||||
|
||||
func (config Config) ConstructQemuArgs() []string {
|
||||
@@ -171,7 +173,7 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
}
|
||||
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
args = append(args, "-append", strconv.Quote("earlyprintk=serial console=ttyS0"))
|
||||
args = append(args, "-append", strconv.Quote("quiet console=null rootfstype=ramfs"))
|
||||
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
|
||||
|
||||
// SEV
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
)
|
||||
@@ -43,34 +42,6 @@ func (v *qemuVM) Start() error {
|
||||
qemuCfg.NetDevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.NetDevConfig.ID, id)
|
||||
qemuCfg.SevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.SevConfig.ID, id)
|
||||
|
||||
if !v.config.KernelHash {
|
||||
// Copy firmware vars file
|
||||
srcFile := qemuCfg.OVMFVarsConfig.File
|
||||
dstFile := fmt.Sprintf("%s/%s-%s.fd", v.config.TmpFileLoc, firmwareVars, id)
|
||||
err = internal.CopyFile(srcFile, dstFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qemuCfg.OVMFVarsConfig.File = dstFile
|
||||
}
|
||||
|
||||
// Copy img files
|
||||
srcFile := qemuCfg.DiskImgConfig.KernelFile
|
||||
dstFile := fmt.Sprintf("%s/%s-%s", v.config.TmpFileLoc, KernelFile, id)
|
||||
err = internal.CopyFile(srcFile, dstFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qemuCfg.DiskImgConfig.KernelFile = dstFile
|
||||
|
||||
srcFile = qemuCfg.DiskImgConfig.RootFsFile
|
||||
dstFile = fmt.Sprintf("%s/%s-%s.gz", v.config.TmpFileLoc, rootfsFile, id)
|
||||
err = internal.CopyFile(srcFile, dstFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qemuCfg.DiskImgConfig.RootFsFile = dstFile
|
||||
|
||||
exe, args, err := v.executableAndArgs()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+95
-29
@@ -9,7 +9,9 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
@@ -53,30 +55,42 @@ type Service interface {
|
||||
Stop(ctx context.Context, computationID string) error
|
||||
// RetrieveAgentEventsLogs Retrieve and forward agent logs and events via vsock.
|
||||
RetrieveAgentEventsLogs()
|
||||
// FetchBackendInfo measures and fetches the backend information.
|
||||
FetchBackendInfo() ([]byte, error)
|
||||
}
|
||||
|
||||
type managerService struct {
|
||||
qemuCfg qemu.Config
|
||||
logger *slog.Logger
|
||||
agents map[int]string // agent map of vsock cid to computationID.
|
||||
eventsChan chan *manager.ClientStreamMessage
|
||||
vms map[string]vm.VM
|
||||
vmFactory vm.Provider
|
||||
qemuCfg qemu.Config
|
||||
backendMeasurementBinaryPath string
|
||||
logger *slog.Logger
|
||||
agents map[int]string // agent map of vsock cid to computationID.
|
||||
eventsChan chan *manager.ClientStreamMessage
|
||||
vms map[string]vm.VM
|
||||
vmFactory vm.Provider
|
||||
portRangeMin int
|
||||
portRangeMax int
|
||||
}
|
||||
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(qemuCfg qemu.Config, logger *slog.Logger, eventsChan chan *manager.ClientStreamMessage, vmFactory vm.Provider) Service {
|
||||
ms := &managerService{
|
||||
qemuCfg: qemuCfg,
|
||||
logger: logger,
|
||||
agents: make(map[int]string),
|
||||
vms: make(map[string]vm.VM),
|
||||
eventsChan: eventsChan,
|
||||
vmFactory: vmFactory,
|
||||
func New(cfg qemu.Config, backendMeasurementBinPath string, logger *slog.Logger, eventsChan chan *manager.ClientStreamMessage, vmFactory vm.Provider) (Service, error) {
|
||||
start, end, err := decodeRange(cfg.HostFwdRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ms
|
||||
ms := &managerService{
|
||||
qemuCfg: cfg,
|
||||
logger: logger,
|
||||
agents: make(map[int]string),
|
||||
vms: make(map[string]vm.VM),
|
||||
eventsChan: eventsChan,
|
||||
vmFactory: vmFactory,
|
||||
backendMeasurementBinaryPath: backendMeasurementBinPath,
|
||||
portRangeMin: start,
|
||||
portRangeMax: end,
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq) (string, error) {
|
||||
@@ -102,14 +116,14 @@ func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq)
|
||||
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
|
||||
return "", errInvalidHashLength
|
||||
}
|
||||
ac.Datasets = append(ac.Datasets, agent.Dataset{Hash: [hashLength]byte(data.Hash), UserKey: data.UserKey})
|
||||
ac.Datasets = append(ac.Datasets, agent.Dataset{Hash: [hashLength]byte(data.Hash), UserKey: data.UserKey, Filename: data.Filename})
|
||||
}
|
||||
|
||||
for _, rc := range c.ResultConsumers {
|
||||
ac.ResultConsumers = append(ac.ResultConsumers, agent.ResultConsumer{UserKey: rc.UserKey})
|
||||
}
|
||||
|
||||
agentPort, err := getFreePort()
|
||||
agentPort, err := getFreePort(ms.portRangeMin, ms.portRangeMax)
|
||||
if err != nil {
|
||||
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
|
||||
return "", errors.Wrap(ErrFailedToAllocatePort, err)
|
||||
@@ -151,30 +165,58 @@ func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq)
|
||||
func (ms *managerService) Stop(ctx context.Context, computationID string) error {
|
||||
cvm, ok := ms.vms[computationID]
|
||||
if !ok {
|
||||
defer ms.publishEvent("stop-computation", computationID, "failed", json.RawMessage{})
|
||||
return ErrNotFound
|
||||
}
|
||||
if err := cvm.Stop(); err != nil {
|
||||
defer ms.publishEvent("stop-computation", computationID, "failed", json.RawMessage{})
|
||||
return err
|
||||
}
|
||||
delete(ms.vms, computationID)
|
||||
defer ms.publishEvent("stop-computation", computationID, "complete", json.RawMessage{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFreePort() (int, error) {
|
||||
listener, err := net.Listen("tcp", "")
|
||||
func getFreePort(minPort, maxPort int) (int, error) {
|
||||
if checkPortisFree(minPort) {
|
||||
return minPort, nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
portCh := make(chan int, maxPort-minPort+1)
|
||||
|
||||
for port := minPort; port <= maxPort; port++ {
|
||||
wg.Add(1)
|
||||
go func(p int) {
|
||||
defer wg.Done()
|
||||
|
||||
if checkPortisFree(p) {
|
||||
portCh <- p
|
||||
}
|
||||
}(port)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(portCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case port := <-portCh:
|
||||
return port, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("failed to find free port in range %d-%d", minPort, maxPort)
|
||||
}
|
||||
}
|
||||
|
||||
func checkPortisFree(port int) bool {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return false
|
||||
}
|
||||
defer listener.Close()
|
||||
_, portStr, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return port, nil
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ms *managerService) publishEvent(event, cmpID, status string, details json.RawMessage) {
|
||||
@@ -200,3 +242,27 @@ func computationHash(ac agent.Computation) ([32]byte, error) {
|
||||
|
||||
return sha3.Sum256(jsonData), nil
|
||||
}
|
||||
|
||||
func decodeRange(input string) (int, int, error) {
|
||||
re := regexp.MustCompile(`(\d+)-(\d+)`)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
if len(matches) != 3 {
|
||||
return 0, 0, fmt.Errorf("invalid input format: %s", input)
|
||||
}
|
||||
|
||||
start, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
end, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if start > end {
|
||||
return 0, 0, fmt.Errorf("invalid range: %d-%d", start, end)
|
||||
}
|
||||
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
"github.com/ultravioletrs/cocos/manager/vm/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
cfg := qemu.Config{
|
||||
HostFwdRange: "6000-6100",
|
||||
}
|
||||
logger := slog.Default()
|
||||
eventsChan := make(chan *manager.ClientStreamMessage)
|
||||
vmf := new(mocks.Provider)
|
||||
|
||||
service, err := New(cfg, "", logger, eventsChan, vmf.Execute)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
assert.IsType(t, &managerService{}, service)
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
vmf := new(mocks.Provider)
|
||||
vmMock := new(mocks.VM)
|
||||
vmf.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return(vmMock)
|
||||
tests := []struct {
|
||||
name string
|
||||
req *manager.ComputationRunReq
|
||||
vmStartError error
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Successful run",
|
||||
req: &manager.ComputationRunReq{
|
||||
Id: "test-computation",
|
||||
Name: "Test Computation",
|
||||
Algorithm: &manager.Algorithm{
|
||||
Hash: make([]byte, hashLength),
|
||||
},
|
||||
AgentConfig: &manager.AgentConfig{},
|
||||
},
|
||||
vmStartError: nil,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "VM start failure",
|
||||
req: &manager.ComputationRunReq{
|
||||
Id: "test-computation",
|
||||
Name: "Test Computation",
|
||||
Algorithm: &manager.Algorithm{
|
||||
Hash: make([]byte, hashLength),
|
||||
},
|
||||
AgentConfig: &manager.AgentConfig{},
|
||||
},
|
||||
vmStartError: assert.AnError,
|
||||
expectedError: assert.AnError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.vmStartError == nil {
|
||||
vmMock.On("Start").Return(nil).Once()
|
||||
} else {
|
||||
vmMock.On("Start").Return(tt.vmStartError).Once()
|
||||
}
|
||||
|
||||
vmMock.On("SendAgentConfig", mock.Anything).Return(nil)
|
||||
|
||||
qemuCfg := qemu.Config{
|
||||
VSockConfig: qemu.VSockConfig{
|
||||
GuestCID: 3,
|
||||
Vnc: 5900,
|
||||
},
|
||||
}
|
||||
logger := slog.Default()
|
||||
eventsChan := make(chan *manager.ClientStreamMessage, 10)
|
||||
|
||||
ms := &managerService{
|
||||
qemuCfg: qemuCfg,
|
||||
logger: logger,
|
||||
agents: make(map[int]string),
|
||||
vms: make(map[string]vm.VM),
|
||||
eventsChan: eventsChan,
|
||||
vmFactory: vmf.Execute,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
port, err := ms.Run(ctx, tt.req)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, tt.expectedError)
|
||||
assert.Empty(t, port)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, port)
|
||||
assert.Len(t, ms.vms, 1)
|
||||
assert.Len(t, ms.agents, 1)
|
||||
}
|
||||
|
||||
vmf.AssertExpectations(t)
|
||||
|
||||
for len(eventsChan) > 0 {
|
||||
<-eventsChan
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStop(t *testing.T) {
|
||||
vmf := new(mocks.Provider)
|
||||
vmMock := new(mocks.VM)
|
||||
vmf.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return(vmMock)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
computationID string
|
||||
vmStopError error
|
||||
expectedError error
|
||||
initialVMCount int
|
||||
}{
|
||||
{
|
||||
name: "Successful stop",
|
||||
computationID: "existing-computation",
|
||||
vmStopError: nil,
|
||||
expectedError: nil,
|
||||
initialVMCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Non-existent computation",
|
||||
computationID: "non-existent-computation",
|
||||
vmStopError: nil,
|
||||
expectedError: ErrNotFound,
|
||||
initialVMCount: 0,
|
||||
},
|
||||
{
|
||||
name: "VM stop error",
|
||||
computationID: "error-computation",
|
||||
vmStopError: assert.AnError,
|
||||
expectedError: assert.AnError,
|
||||
initialVMCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger := slog.Default()
|
||||
eventsChan := make(chan *manager.ClientStreamMessage, 10)
|
||||
ms := &managerService{
|
||||
logger: logger,
|
||||
vms: make(map[string]vm.VM),
|
||||
eventsChan: eventsChan,
|
||||
}
|
||||
vmMock := new(mocks.VM)
|
||||
|
||||
if tt.vmStopError == nil {
|
||||
vmMock.On("Stop").Return(nil).Once()
|
||||
} else {
|
||||
vmMock.On("Stop").Return(assert.AnError).Once()
|
||||
}
|
||||
|
||||
if tt.initialVMCount > 0 {
|
||||
ms.vms[tt.computationID] = vmMock
|
||||
}
|
||||
|
||||
err := ms.Stop(context.Background(), tt.computationID)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, ms.vms, 0)
|
||||
}
|
||||
|
||||
for len(eventsChan) > 0 {
|
||||
<-eventsChan
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFreePort(t *testing.T) {
|
||||
port, err := getFreePort(6000, 6100)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, port, 0)
|
||||
}
|
||||
|
||||
func TestPublishEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
event string
|
||||
computationID string
|
||||
status string
|
||||
details json.RawMessage
|
||||
}{
|
||||
{
|
||||
name: "Standard event",
|
||||
event: "test-event",
|
||||
computationID: "test-computation",
|
||||
status: "test-status",
|
||||
details: nil,
|
||||
},
|
||||
{
|
||||
name: "Event with details",
|
||||
event: "detailed-event",
|
||||
computationID: "detailed-computation",
|
||||
status: "detailed-status",
|
||||
details: json.RawMessage(`{"key": "value"}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eventsChan := make(chan *manager.ClientStreamMessage, 1)
|
||||
ms := &managerService{
|
||||
eventsChan: eventsChan,
|
||||
}
|
||||
|
||||
ms.publishEvent(tt.event, tt.computationID, tt.status, tt.details)
|
||||
|
||||
assert.Len(t, eventsChan, 1)
|
||||
event := <-eventsChan
|
||||
assert.Equal(t, tt.event, event.GetAgentEvent().EventType)
|
||||
assert.Equal(t, tt.computationID, event.GetAgentEvent().ComputationId)
|
||||
assert.Equal(t, tt.status, event.GetAgentEvent().Status)
|
||||
assert.Equal(t, "manager", event.GetAgentEvent().Originator)
|
||||
assert.Equal(t, tt.details, json.RawMessage(event.GetAgentEvent().Details))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package manager_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
managergrpc "github.com/ultravioletrs/cocos/manager/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
const (
|
||||
bufSize = 1024 * 1024
|
||||
keyBitSize = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
lis *bufconn.Listener
|
||||
algoPath = "../test/manual/algo/lin_reg.py"
|
||||
dataPath = "../test/manual/data/iris.csv"
|
||||
attestedTLS = false
|
||||
)
|
||||
|
||||
type svc struct {
|
||||
logger *slog.Logger
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
logger := mglog.NewMock()
|
||||
|
||||
lis = bufconn.Listen(bufSize)
|
||||
s := grpc.NewServer()
|
||||
|
||||
manager.RegisterManagerServiceServer(s, managergrpc.NewServer(make(chan *manager.ClientStreamMessage, 1), &svc{logger: logger}))
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
code := m.Run()
|
||||
|
||||
s.Stop()
|
||||
lis.Close()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func bufDialer(context.Context, string) (net.Conn, error) {
|
||||
return lis.Dial()
|
||||
}
|
||||
|
||||
func (s *svc) Run(ipAddress string, runReqChan chan *manager.ServerStreamMessage, authInfo credentials.AuthInfo) {
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, keyBitSize)
|
||||
if err != nil {
|
||||
s.t.Fatalf("Error generating public key: %v", err)
|
||||
}
|
||||
|
||||
pubKey, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
|
||||
if err != nil {
|
||||
s.t.Fatalf("Error marshalling public key: %v", err)
|
||||
}
|
||||
|
||||
pubPemBytes := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: pubKey,
|
||||
})
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
runReqChan <- &manager.ServerStreamMessage{
|
||||
Message: &manager.ServerStreamMessage_TerminateReq{
|
||||
TerminateReq: &manager.Terminate{Message: "test terminate"},
|
||||
},
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
if err != nil {
|
||||
s.t.Fatalf("failed to read algorithm file: %s", err)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(dataPath)
|
||||
if err != nil {
|
||||
s.t.Fatalf("failed to read data file: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
pubPem, _ := pem.Decode(pubPemBytes)
|
||||
algoHash := sha3.Sum256(algo)
|
||||
dataHash := sha3.Sum256(data)
|
||||
runReqChan <- &manager.ServerStreamMessage{
|
||||
Message: &manager.ServerStreamMessage_RunReq{
|
||||
RunReq: &manager.ComputationRunReq{
|
||||
Id: "1",
|
||||
Name: "sample computation",
|
||||
Description: "sample description",
|
||||
Datasets: []*manager.Dataset{{Hash: dataHash[:], UserKey: pubPem.Bytes}},
|
||||
Algorithm: &manager.Algorithm{Hash: algoHash[:], UserKey: pubPem.Bytes},
|
||||
ResultConsumers: []*manager.ResultConsumer{{UserKey: pubPem.Bytes}},
|
||||
AgentConfig: &manager.AgentConfig{
|
||||
Port: "7002",
|
||||
LogLevel: "debug",
|
||||
AttestedTls: attestedTLS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -39,3 +39,10 @@ func (tm *tracingMiddleware) Stop(ctx context.Context, computationID string) err
|
||||
func (tm *tracingMiddleware) RetrieveAgentEventsLogs() {
|
||||
tm.svc.RetrieveAgentEventsLogs()
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) FetchBackendInfo() ([]byte, error) {
|
||||
_, span := tm.tracer.Start(context.Background(), "fetch_backend_info")
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.FetchBackendInfo()
|
||||
}
|
||||
|
||||
+549
-189
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v4.25.3
|
||||
// - protoc v5.27.2
|
||||
// source: manager/manager.proto
|
||||
|
||||
package manager
|
||||
|
||||
@@ -108,9 +108,9 @@ func (p *ProgressBar) SendAlgorithm(description string, algobuffer, reqBuffer *b
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *ProgressBar) SendData(description string, buffer *bytes.Buffer, stream *agent.AgentService_DataClient) error {
|
||||
func (p *ProgressBar) SendData(description, filename string, buffer *bytes.Buffer, stream *agent.AgentService_DataClient) error {
|
||||
return p.sendData(description, buffer, &dataClientWrapper{client: stream}, func(data []byte) interface{} {
|
||||
return &agent.DataRequest{Dataset: data}
|
||||
return &agent.DataRequest{Dataset: data, Filename: filename}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -205,28 +205,28 @@ func (p *ProgressBar) renderProgressBar() error {
|
||||
}
|
||||
|
||||
if err := p.clearProgressBar(); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to clear progress bar: %v", err)
|
||||
}
|
||||
|
||||
// The progress bar starts with the description.
|
||||
if _, err := builder.WriteString(p.description); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add description: %v", err)
|
||||
}
|
||||
|
||||
// Add dots to progress bar.
|
||||
if _, err := builder.WriteString(progressBarDots); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add dots: %v", err)
|
||||
}
|
||||
|
||||
// Add uploaded percentage.
|
||||
strCurrentUploadPercentage := fmt.Sprintf("%4d%% ", p.currentUploadPercentage)
|
||||
if _, err := builder.WriteString(strCurrentUploadPercentage); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add upload percentage bracket: %v", err)
|
||||
}
|
||||
|
||||
// Add letf bracket and space to progress bar.
|
||||
if _, err := builder.WriteString(leftBracket); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add left bracket: %v", err)
|
||||
}
|
||||
|
||||
progressWidth := width - builder.Len() - len(rightBracket+" ")
|
||||
@@ -242,12 +242,12 @@ func (p *ProgressBar) renderProgressBar() error {
|
||||
|
||||
// Add progress to the progress bar.
|
||||
if _, err := builder.WriteString(progress); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add progress strings to padding: %v", err)
|
||||
}
|
||||
|
||||
// Add head to progress bar.
|
||||
if _, err := builder.WriteString(head); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add head to padding: %v", err)
|
||||
}
|
||||
|
||||
// Add padding to end of bar.
|
||||
@@ -255,17 +255,17 @@ func (p *ProgressBar) renderProgressBar() error {
|
||||
|
||||
// Add padding to progress bar.
|
||||
if _, err := builder.WriteString(padding); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add padding: %v", err)
|
||||
}
|
||||
|
||||
// Add right bracket to progress bar.
|
||||
if _, err := builder.WriteString(rightBracket); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add right bracket: %v", err)
|
||||
}
|
||||
|
||||
// Write progress bar.
|
||||
if _, err := io.WriteString(os.Stdout, builder.String()); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to write string: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+5
-2
@@ -82,7 +82,10 @@ func (sdk *agentSDK) Data(ctx context.Context, dataset agent.Dataset, privKey an
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||
for k, v := range md {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, k, v[0])
|
||||
}
|
||||
|
||||
stream, err := sdk.client.Data(ctx)
|
||||
if err != nil {
|
||||
sdk.logger.Error("Failed to call Data RPC")
|
||||
@@ -91,7 +94,7 @@ func (sdk *agentSDK) Data(ctx context.Context, dataset agent.Dataset, privKey an
|
||||
dataBuffer := bytes.NewBuffer(dataset.Dataset)
|
||||
|
||||
pb := progressbar.New()
|
||||
if err := pb.SendData(dataProgressBarDescription, dataBuffer, &stream); err != nil {
|
||||
if err := pb.SendData(dataProgressBarDescription, dataset.Filename, dataBuffer, &stream); err != nil {
|
||||
sdk.logger.Error("Failed to send Data")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
algoPath = "../../test/manual/algo/lin_reg.py"
|
||||
dataPath = "../../test/manual/data/iris.csv"
|
||||
|
||||
errInappropriateIoctl = errors.New("inappropriate ioctl for device")
|
||||
errInappropriateIoctl = errors.New("failed to get terminal width: inappropriate ioctl for device")
|
||||
)
|
||||
|
||||
func TestAlgo(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package backendinfo
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed backend_info.json
|
||||
var BackendInfo []byte
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"snp_policy": {
|
||||
"policy": 1966081,
|
||||
"family_id": [
|
||||
0
|
||||
],
|
||||
"image_id": [
|
||||
0
|
||||
],
|
||||
"vmpl": {
|
||||
"value": 0
|
||||
},
|
||||
"minimum_tcb": 15063977803600887811,
|
||||
"minimum_launch_tcb": 15063977803600887811,
|
||||
"require_author_key": false,
|
||||
"measurement": [
|
||||
0
|
||||
],
|
||||
"host_data": [
|
||||
0
|
||||
],
|
||||
"report_id_ma": [
|
||||
0
|
||||
],
|
||||
"chip_id": [
|
||||
26,
|
||||
177,
|
||||
106,
|
||||
181,
|
||||
15,
|
||||
165,
|
||||
174,
|
||||
66,
|
||||
236,
|
||||
140,
|
||||
27,
|
||||
37,
|
||||
187,
|
||||
218,
|
||||
92,
|
||||
11,
|
||||
165,
|
||||
234,
|
||||
146,
|
||||
187,
|
||||
69,
|
||||
89,
|
||||
141,
|
||||
64,
|
||||
172,
|
||||
132,
|
||||
62,
|
||||
35,
|
||||
136,
|
||||
46,
|
||||
129,
|
||||
2,
|
||||
44,
|
||||
188,
|
||||
33,
|
||||
180,
|
||||
169,
|
||||
233,
|
||||
18,
|
||||
188,
|
||||
75,
|
||||
68,
|
||||
224,
|
||||
255,
|
||||
210,
|
||||
45,
|
||||
34,
|
||||
122,
|
||||
152,
|
||||
115,
|
||||
105,
|
||||
58,
|
||||
70,
|
||||
52,
|
||||
48,
|
||||
121,
|
||||
198,
|
||||
166,
|
||||
252,
|
||||
245,
|
||||
58,
|
||||
69,
|
||||
126,
|
||||
147
|
||||
],
|
||||
"minimum_build": 7,
|
||||
"minimum_version": "1.55",
|
||||
"permit_provisional_firmware": false,
|
||||
"require_id_block": false
|
||||
},
|
||||
"root_of_trust": {
|
||||
"product": "Milan",
|
||||
"check_crl": true,
|
||||
"disallow_network": false
|
||||
}
|
||||
}
|
||||
+12
-10
@@ -13,11 +13,11 @@ import (
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
|
||||
managergrpc "github.com/ultravioletrs/cocos/manager/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -44,11 +44,6 @@ type svc struct {
|
||||
|
||||
func (s *svc) Run(ipAdress string, reqChan chan *manager.ServerStreamMessage, auth credentials.AuthInfo) {
|
||||
s.logger.Debug(fmt.Sprintf("received who am on ip address %s", ipAdress))
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to read algorithm file: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
pubKey, err := os.ReadFile(pubKeyFile)
|
||||
if err != nil {
|
||||
@@ -63,16 +58,21 @@ func (s *svc) Run(ipAdress string, reqChan chan *manager.ServerStreamMessage, au
|
||||
s.logger.Error(fmt.Sprintf("data file does not exist: %s", dataPath))
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(dataPath)
|
||||
dataHash, err := internal.Checksum(dataPath)
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to read data file: %s", err))
|
||||
s.logger.Error(fmt.Sprintf("failed to calculate checksum: %s", err))
|
||||
return
|
||||
}
|
||||
dataHash := sha3.Sum256(data)
|
||||
|
||||
datasets = append(datasets, &manager.Dataset{Hash: dataHash[:], UserKey: pubPem.Bytes})
|
||||
}
|
||||
|
||||
algoHash := sha3.Sum256(algo)
|
||||
algoHash, err := internal.Checksum(algoPath)
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to calculate checksum: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
reqChan <- &manager.ServerStreamMessage{
|
||||
Message: &manager.ServerStreamMessage_RunReq{
|
||||
RunReq: &manager.ComputationRunReq{
|
||||
@@ -126,6 +126,8 @@ func main() {
|
||||
fmt.Println("received agent event")
|
||||
case *manager.ClientStreamMessage_AgentLog:
|
||||
fmt.Println("received agent log")
|
||||
case *manager.ClientStreamMessage_BackendInfo:
|
||||
fmt.Println("received backend info measurement request")
|
||||
}
|
||||
fmt.Println(incoming.Message)
|
||||
}
|
||||
|
||||
+41
-23
@@ -3,15 +3,17 @@
|
||||
## CLI
|
||||
|
||||
Throughout the tests, we assume that our current working directory is the root of the `cocos` repository, both on the host machine and in the VM.
|
||||
First, we will build cli by running in the root directory of `cocos`:
|
||||
```bash
|
||||
make cli
|
||||
```
|
||||
|
||||
The cli will be compiled to the build directory `./build/cocos-cli`.
|
||||
|
||||
### Algorithm requirements
|
||||
|
||||
Agent accepts the algorithm as a binary that take in two command line arguments.
|
||||
```shell
|
||||
algorithm-file <unix socket path> <dataset file paths>
|
||||
```
|
||||
|
||||
The algorithm program should return the results to a socket and an example can be seen in this [file](./algo/lin_reg.py).
|
||||
Agent accepts the algorithm as a binary or python or wasm file.
|
||||
All assets/datasets the algorithm uses are stored in the `datasets` directory. The results from the algorithm run should be stored in the `results` directory. All these paths are relative to the algorithm working directory.
|
||||
|
||||
### Agent-CLI interaction
|
||||
|
||||
@@ -32,7 +34,7 @@ sev-snp-measure --mode snp --vcpus 4 --vcpu-type EPYC-v4 --ovmf $OVMF_CODE --ker
|
||||
|
||||
To speed up the verification process of attested TLS, download the ARK and ASK certificates using the CLI tool. The CLI tool will download the certificates under your home directory in the `.cocos` directory.
|
||||
```bash
|
||||
go run cmd/cli/main.go ca-bundle <path/to/platfrom_info.json>
|
||||
./build/cocos-cli ca-bundle <path/to/platfrom_info.json>
|
||||
```
|
||||
|
||||
In the following text, we can see an example of how the CLI tool is used.
|
||||
@@ -52,55 +54,52 @@ cd ../..
|
||||
# The CLI should also be aware of the VM measurement. To add the measurement
|
||||
# to the .json file that contains the information about the platform, run CLI
|
||||
# with the measurement in base64 format and the path of the backend_info.json file.:
|
||||
go run cmd/cli/main.go backend measurement '<measurement>' '<backend_info.json>'
|
||||
./build/cocos-cli backend measurement '<measurement>' '<backend_info.json>'
|
||||
|
||||
# If the VM is booted with the QEMU host data option, the CLI should also know
|
||||
# the host data information. To add the host data to the .json file that contains
|
||||
# the information about the platform, run CLI with the host data in base64 format
|
||||
# and the path of the backend_info.json file.:
|
||||
go run cmd/cli/main.go backend measurement '<host-data>' '<backend_info.json>'
|
||||
./build/cocos-cli backend hostdata '<host-data>' '<backend_info.json>'
|
||||
|
||||
# For attested TLS, also define the path to the backend_info.json that contains reference values for the fields of the attestation report
|
||||
export AGENT_GRPC_MANIFEST=./scripts/backend_info/backend_info.json
|
||||
export AGENT_GRPC_ATTESTED_TLS=true
|
||||
|
||||
# Retieve Attestation
|
||||
go run cmd/cli/main.go attestation get '<report_data>'
|
||||
./build/cocos-cli attestation get '<report_data>'
|
||||
|
||||
# Validate Attestation
|
||||
# Product name must be Milan or Genoa
|
||||
go run cmd/cli/main.go attestation validate '<attesation>' --report_data '<report_data>' --product <product_name>
|
||||
./build/cocos-cli attestation validate '<attesation>' --report_data '<report_data>' --product <product_name>
|
||||
|
||||
# Run the CLI program with algorithm input
|
||||
go run cmd/cli/main.go algo test/manual/algo/lin_reg.bin <private_key_file_path>
|
||||
./build/cocos-cli algo test/manual/algo/lin_reg.py <private_key_file_path> -a python -r test/manual/algo/requirements.py
|
||||
# 2023/09/21 10:43:53 Uploading algorithm binary: test/manual/algo/lin_reg.bin
|
||||
|
||||
# In order to run the Docker image, run the CLI program with the algorithm docker option
|
||||
go run ./cmd/cli/main.go algo -a docker <path_to_docker_image.tar> <private_key_file_path>
|
||||
# 2023/09/21 10:43:53 Uploading algorithm binary: <path_to_docker_image.tar>
|
||||
|
||||
# Run the CLI program with dataset input
|
||||
go run cmd/cli/main.go data test/manual/data/iris.csv <private_key_file_path>
|
||||
go run cmd/cli/main.go data test/manual/data/iris.csv <private_key_file_path>
|
||||
./build/cocos-cli data test/manual/data/iris.csv <private_key_file_path>
|
||||
# 2023/09/21 10:45:25 Uploading dataset CSV: test/manual/data/iris.csv
|
||||
|
||||
# Run the CLI program to fetch computation result
|
||||
go run cmd/cli/main.go result <private_key_file_path>
|
||||
./build/cocos-cli result <private_key_file_path>
|
||||
# 2023/09/21 10:45:39 Retrieving computation result file
|
||||
# 2023/09/21 10:45:40 Computation result retrieved and saved successfully!
|
||||
```
|
||||
|
||||
Now there is a `result.bin` file in the current working directory. The file holds the trained logistic regression model. To test the model, run
|
||||
Now there is a `result.zip` file in the current working directory. The file holds the trained logistic regression model. To test the model, run
|
||||
|
||||
```sh
|
||||
python3 test/manual/algo/lin_reg_test.py test/manual/data/iris.csv result.bin
|
||||
python ./test/manual/algo/lin_reg.py predict results.zip ./test/manual/data
|
||||
```
|
||||
|
||||
You should get an output (truncated for the sake of brevity):
|
||||
|
||||
```sh
|
||||
Id SepalLengthCm SepalWidthCm PetalLengthCm PetalWidthCm Species
|
||||
0 1 5.1 3.5 1.4 0.2 Iris-setosa
|
||||
1 2 4.9 3.0 1.4 0.2 Iris-setosa
|
||||
2 3 4.7 3.2 1.3 0.2 Iris-setosa
|
||||
3 4 4.6 3.1 1.5 0.2 Iris-setosa
|
||||
4 5 5.0 3.6 1.4 0.2 Iris-setosa
|
||||
Precision, Recall, Confusion matrix, in training
|
||||
|
||||
precision recall f1-score support
|
||||
@@ -112,4 +111,23 @@ Iris-versicolor 0.923 0.889 0.906 27
|
||||
accuracy 0.933 75
|
||||
macro avg 0.939 0.938 0.938 75
|
||||
weighted avg 0.934 0.933 0.933 75
|
||||
|
||||
[[21 0 0]
|
||||
[ 0 24 3]
|
||||
[ 0 2 25]]
|
||||
Precision, Recall, Confusion matrix, in testing
|
||||
|
||||
precision recall f1-score support
|
||||
|
||||
Iris-setosa 1.000 1.000 1.000 29
|
||||
Iris-versicolor 1.000 1.000 1.000 23
|
||||
Iris-virginica 1.000 1.000 1.000 23
|
||||
|
||||
accuracy 1.000 75
|
||||
macro avg 1.000 1.000 1.000 75
|
||||
weighted avg 1.000 1.000 1.000 75
|
||||
|
||||
[[29 0 0]
|
||||
[ 0 23 0]
|
||||
[ 0 0 23]]
|
||||
```
|
||||
|
||||
@@ -10,15 +10,16 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mdlayher/vsock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -31,25 +32,23 @@ func main() {
|
||||
pubKeyFile := os.Args[3]
|
||||
attestedTLSParam, err := strconv.ParseBool(os.Args[4])
|
||||
if err != nil {
|
||||
log.Fatalf("usage: %s <data-path> <algo-path> <attested-tls-bool>, <attested-tls-bool> must be a bool value", os.Args[0])
|
||||
log.Fatalf("usage: %s <data-path> <algo-path> <public-key-path> <attested-tls-bool>, <attested-tls-bool> must be a bool value", os.Args[0])
|
||||
}
|
||||
attestedTLS := attestedTLSParam
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
if err != nil {
|
||||
log.Fatalf(fmt.Sprintf("failed to read algorithm file: %s", err))
|
||||
}
|
||||
data, err := os.ReadFile(dataPath)
|
||||
if err != nil {
|
||||
log.Fatalf(fmt.Sprintf("failed to read data file: %s", err))
|
||||
}
|
||||
pubKey, err := os.ReadFile(pubKeyFile)
|
||||
if err != nil {
|
||||
log.Fatalf(fmt.Sprintf("failed to read public key file: %s", err))
|
||||
}
|
||||
pubPem, _ := pem.Decode(pubKey)
|
||||
algoHash := sha3.Sum256(algo)
|
||||
dataHash := sha3.Sum256(data)
|
||||
algoHash, err := internal.Checksum(algoPath)
|
||||
if err != nil {
|
||||
log.Fatalf(fmt.Sprintf("failed to calculate checksum: %s", err))
|
||||
}
|
||||
dataHash, err := internal.Checksum(dataPath)
|
||||
if err != nil {
|
||||
log.Fatalf(fmt.Sprintf("failed to calculate checksum: %s", err))
|
||||
}
|
||||
|
||||
l, err := vsock.Listen(manager.ManagerVsockPort, nil)
|
||||
if err != nil {
|
||||
@@ -57,8 +56,8 @@ func main() {
|
||||
}
|
||||
ac := agent.Computation{
|
||||
ID: "123",
|
||||
Datasets: agent.Datasets{agent.Dataset{Hash: dataHash, UserKey: pubPem.Bytes}},
|
||||
Algorithm: agent.Algorithm{Hash: algoHash, UserKey: pubPem.Bytes},
|
||||
Datasets: agent.Datasets{agent.Dataset{Hash: [32]byte(dataHash), UserKey: pubPem.Bytes}},
|
||||
Algorithm: agent.Algorithm{Hash: [32]byte(algoHash), UserKey: pubPem.Bytes},
|
||||
ResultConsumers: []agent.ResultConsumer{{UserKey: pubPem.Bytes}},
|
||||
AgentConfig: agent.AgentConfig{
|
||||
LogLevel: "debug",
|
||||
@@ -66,7 +65,9 @@ func main() {
|
||||
AttestedTls: attestedTLS,
|
||||
},
|
||||
}
|
||||
fmt.Println(SendAgentConfig(3, ac))
|
||||
if err := SendAgentConfig(3, ac); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
@@ -74,18 +75,7 @@ func main() {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
b := make([]byte, 1024)
|
||||
n, err := conn.Read(b)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
var mes pkgmanager.ClientStreamMessage
|
||||
if err := proto.Unmarshal(b[:n], &mes); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
fmt.Println(mes.String())
|
||||
go handleConnections(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,3 +99,21 @@ func SendAgentConfig(cid uint32, ac agent.Computation) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConnections(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
for {
|
||||
b := make([]byte, 1024)
|
||||
n, err := conn.Read(b)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
var message pkgmanager.ClientStreamMessage
|
||||
if err := proto.Unmarshal(b[:n], &message); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(message.String())
|
||||
}
|
||||
}
|
||||
|
||||
+170
-10
@@ -1,17 +1,177 @@
|
||||
# Algorithm
|
||||
|
||||
Agent accepts binaries programs. To use the python program you need to bundle or compile it.
|
||||
In this example we'll use [pyinstaller](https://pypi.org/project/pyinstaller/)
|
||||
Agent accepts binaries programs, python scripts, Docker images and wasm files. It runs them in a sandboxed environment and returns the output.
|
||||
|
||||
```shell
|
||||
pip install pandas scikit-learn
|
||||
pip install -U pyinstaller
|
||||
pyinstaller --onefile lin_reg.py
|
||||
## Python Example
|
||||
|
||||
To test this examples work on your local machine, you need to install the following dependencies:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Make the binary static:
|
||||
This can be done in a virtual environment.
|
||||
|
||||
```shell
|
||||
pip install staticx
|
||||
staticx <dynamic_binary_file_path> <output_file_path>
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
To run the example, you can use the following command:
|
||||
|
||||
```bash
|
||||
python3 test/manual/algo/addition.py
|
||||
```
|
||||
|
||||
The addition example is a simple algorithm to demonstrate you can run an algorithm without any external dependencies and input arguments. It returns the sum of two numbers.
|
||||
|
||||
```bash
|
||||
python3 test/manual/algo/lin_reg.py
|
||||
```
|
||||
|
||||
The linear regression example is a more complex algorithm that requires external dependencies.It returns a linear regression model trained on the iris dataset found [here](../data/) for demonstration purposes.
|
||||
|
||||
```bash
|
||||
python3 test/manual/algo/lin_reg.py predict results.zip test/manual/data
|
||||
```
|
||||
|
||||
This will make inference on the results of the linear regression model.
|
||||
|
||||
To run the examples in the secure VM (SVM) by the Agent, you can use the following command:
|
||||
|
||||
```bash
|
||||
go run ./test/computations/main.go ./test/manual/algo/lin_reg.py public.pem false ./test/manual/data/iris.csv
|
||||
```
|
||||
|
||||
This command is run from the root directory of the project. This will start the computation server.
|
||||
|
||||
In another window, you can run the following command:
|
||||
|
||||
```bash
|
||||
sudo MANAGER_QEMU_SMP_MAXCPUS=4 MANAGER_GRPC_URL=localhost:7001 MANAGER_LOG_LEVEL=debug MANAGER_QEMU_USE_SUDO=false MANAGER_QEMU_ENABLE_SEV=false MANAGER_QEMU_SEV_CBITPOS=51 MANAGER_QEMU_ENABLE_SEV_SNP=false MANAGER_QEMU_OVMF_CODE_FILE=/usr/share/edk2/x64/OVMF_CODE.fd MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/edk2/x64/OVMF_VARS.fd go run main.go
|
||||
```
|
||||
|
||||
This command is run from the [manager main directory](../../../cmd/manager/). This will start the manager. Make sure you have already built the [qemu image](../../../hal/linux/README.md).
|
||||
|
||||
In another window, you can run the following command:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli algo ./test/manual/algo/lin_reg.py ./private.pem -a python -r ./test/manual/algo/requirements.txt
|
||||
```
|
||||
|
||||
make sure you have built the cocos-cli. This will upload the algorithm and the requirements file.
|
||||
|
||||
Next we need to upload the dataset
|
||||
|
||||
```bash
|
||||
./build/cocos-cli data ./test/manual/data/iris.csv ./private.pem
|
||||
```
|
||||
|
||||
After some time when the results are ready, you can run the following command to get the results:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli result ./private.pem
|
||||
```
|
||||
|
||||
This will return the results of the algorithm.
|
||||
|
||||
To make inference on the results, you can use the following command:
|
||||
|
||||
```bash
|
||||
python3 test/manual/algo/lin_reg.py predict results.zip test/manual/data
|
||||
```
|
||||
|
||||
For addition example, you can use the following command:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli ./test/manual/algo/addition.py public.pem false
|
||||
```
|
||||
|
||||
```bash
|
||||
./build/cocos-cli algo ./test/manual/algo/addition.py ./private.pem -a python --args="--a" --args="100" --args="--b" --args="20"
|
||||
```
|
||||
|
||||
```bash
|
||||
./build/cocos-cli result ./private.pem
|
||||
```
|
||||
|
||||
## Docker Example
|
||||
|
||||
Here we will use the docker with the linear regression example (`lin_reg.py`). Throughout the example, we assume that our current working directory is the directory in which the `cocos` repository is cloned. For example:
|
||||
```bash
|
||||
# ls
|
||||
cocos
|
||||
```
|
||||
|
||||
The docker image must have a `cocos` directory containing the `datasets` and `results` directories. The Agent will run this image inside the SVM and will mount the datasets and results onto the `/cocos/datasets` and `/cocos/results` directories inside the image. The docker image must also contain the command that will be run when the docker container is run.
|
||||
|
||||
The first step is to create a docker file. Use your favorite editor to create a file named `Dockerfile` in the current working directory and write in it the following code:
|
||||
|
||||
```bash
|
||||
FROM python:3.9-slim
|
||||
|
||||
# set the working directory in the container
|
||||
WORKDIR /cocos
|
||||
RUN mkdir /cocos/results
|
||||
RUN mkdir /cocos/datasets
|
||||
|
||||
COPY ./cocos/test/manual/algo/requirements.txt /cocos/requirements.txt
|
||||
COPY ./cocos/test/manual/algo/lin_reg.py /cocos/lin_reg.py
|
||||
|
||||
# install dependencies
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# command to be run when the docker container is started
|
||||
CMD ["python3", "/cocos/lin_reg.py"]
|
||||
```
|
||||
|
||||
Next, run the build command and then save the docker image as a `tar` file.
|
||||
```bash
|
||||
docker build -t linreg .
|
||||
docker save linreg > linreg.tar
|
||||
```
|
||||
|
||||
In another window, you can run the following command:
|
||||
|
||||
```bash
|
||||
sudo MANAGER_QEMU_SMP_MAXCPUS=4 MANAGER_GRPC_URL=localhost:7001 MANAGER_LOG_LEVEL=debug MANAGER_QEMU_USE_SUDO=false MANAGER_QEMU_ENABLE_SEV=false MANAGER_QEMU_SEV_CBITPOS=51 MANAGER_QEMU_ENABLE_SEV_SNP=false MANAGER_QEMU_OVMF_CODE_FILE=/usr/share/edk2/x64/OVMF_CODE.fd MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/edk2/x64/OVMF_VARS.fd go run main.go
|
||||
```
|
||||
|
||||
This command is run from the [manager main directory](../../../cmd/manager/). This will start the manager. Make sure you have already built the [qemu image](../../../hal/linux/README.md).
|
||||
|
||||
In another window, specify what kind of algorithm you want the Agent to run (docker):
|
||||
|
||||
```bash
|
||||
./cocos/build/cocos-cli algo ./linreg.tar ./cocos/private.pem -a docker
|
||||
```
|
||||
|
||||
make sure you have built the cocos-cli. This will upload the docker image.
|
||||
|
||||
Next we need to upload the dataset
|
||||
|
||||
```bash
|
||||
./cocos/build/cocos-cli data ./cocos/test/manual/data/iris.csv ./cocos/private.pem
|
||||
```
|
||||
|
||||
After some time when the results are ready, you can run the following command to get the results:
|
||||
|
||||
```bash
|
||||
./cocos/build/cocos-cli results ./cocos/private.pem
|
||||
```
|
||||
|
||||
This will return the results of the algorithm.
|
||||
|
||||
To make inference on the results, you can use the following command:
|
||||
|
||||
```bash
|
||||
python3 ./cocos/test/manual/algo/lin_reg.py predict result.zip ./cocos/test/manual/data
|
||||
```
|
||||
|
||||
## Wasm Example
|
||||
|
||||
More information on how to run wasm files can be found [here](https://github.com/ultravioletrs/ai/tree/main/burn-algorithms).
|
||||
|
||||
## Binary Example
|
||||
|
||||
More information on how to run binary files can be found [here](https://github.com/ultravioletrs/ai/tree/main/burn-algorithms).
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import sys, io
|
||||
import joblib
|
||||
import socket
|
||||
import os
|
||||
import zipfile
|
||||
import argparse
|
||||
|
||||
RESULTS_DIR = "results"
|
||||
RESULTS_FILE = "result.txt"
|
||||
|
||||
|
||||
class Computation:
|
||||
result = 0
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes a new instance of the Computation class.
|
||||
@@ -16,61 +21,52 @@ class Computation:
|
||||
"""
|
||||
self.result = a + b
|
||||
|
||||
def send_result(self, socket_path):
|
||||
def save_result(self):
|
||||
"""
|
||||
Sends the result to a socket.
|
||||
Sends the result to a file.
|
||||
"""
|
||||
buffer = io.BytesIO()
|
||||
|
||||
try:
|
||||
joblib.dump(self.result, buffer)
|
||||
except Exception as e:
|
||||
print("Failed to dump the result to the buffer: ", e)
|
||||
return
|
||||
os.makedirs(RESULTS_DIR)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
data = buffer.getvalue()
|
||||
with open(RESULTS_DIR + os.sep + RESULTS_FILE, "w") as f:
|
||||
f.write(str(self.result))
|
||||
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
try:
|
||||
client.connect(socket_path)
|
||||
except Exception as e:
|
||||
print("Failed to connect to the socket: ", e)
|
||||
return
|
||||
try:
|
||||
client.send(data)
|
||||
except Exception as e:
|
||||
print("Failed to send data to the socket: ", e)
|
||||
return
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def read_results_from_file(self, results_file):
|
||||
"""
|
||||
Reads the results from a file.
|
||||
"""
|
||||
try:
|
||||
results = joblib.load(results_file)
|
||||
print("Results: ", results)
|
||||
except Exception as e:
|
||||
print("Failed to load results from file: ", e)
|
||||
return
|
||||
if results_file.endswith(".zip"):
|
||||
try:
|
||||
os.makedirs(RESULTS_DIR)
|
||||
except FileExistsError:
|
||||
pass
|
||||
with zipfile.ZipFile(results_file, "r") as zip_ref:
|
||||
zip_ref.extractall(RESULTS_DIR)
|
||||
with open(RESULTS_FILE, "r") as f:
|
||||
print(f.read())
|
||||
else:
|
||||
with open(results_file, "r") as f:
|
||||
print(f.read())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = 5
|
||||
b = 10
|
||||
parser = argparse.ArgumentParser(description="Process some integers.")
|
||||
parser.add_argument('--a', type=int, help="First number", default=5)
|
||||
parser.add_argument('--b', type=int, help="Second number", default=10)
|
||||
parser.add_argument('--test', type=str, help="Test with a results file", required=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
computation = Computation()
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
print("Please provide a socket path or a file path")
|
||||
try:
|
||||
if args.test:
|
||||
computation.read_results_from_file(args.test)
|
||||
else:
|
||||
computation.compute(args.a, args.b)
|
||||
computation.save_result()
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
exit(1)
|
||||
|
||||
if sys.argv[1] == "test" and len(sys.argv) == 3:
|
||||
computation.read_results_from_file(sys.argv[2])
|
||||
elif len(sys.argv) == 2:
|
||||
computation.compute(a, b)
|
||||
computation.send_result(sys.argv[1])
|
||||
else:
|
||||
print("Invalid arguments")
|
||||
exit(1)
|
||||
|
||||
|
||||
+100
-31
@@ -1,47 +1,116 @@
|
||||
import sys, io
|
||||
import os
|
||||
import sys
|
||||
import joblib
|
||||
import socket
|
||||
|
||||
import pandas as pd
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
import zipfile
|
||||
from sklearn import metrics
|
||||
|
||||
csv_file_path = sys.argv[2]
|
||||
iris = pd.read_csv(csv_file_path)
|
||||
DATA_DIR = "datasets"
|
||||
RESULTS_DIR = "results"
|
||||
RESULTS_FILE = "model.bin"
|
||||
|
||||
# Droping the Species since we only need the measurements
|
||||
X = iris.drop(['Species'], axis=1)
|
||||
|
||||
# converting into numpy array and assigning petal length and petal width
|
||||
X = X.to_numpy()[:, (3,4)]
|
||||
y = iris['Species']
|
||||
class Computation:
|
||||
model = None
|
||||
|
||||
# Splitting into train and test
|
||||
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.5, random_state=42)
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes a new instance of the Computation class.
|
||||
"""
|
||||
pass
|
||||
|
||||
log_reg = LogisticRegression()
|
||||
log_reg.fit(X_train,y_train)
|
||||
def _read_csv(self, data_path=""):
|
||||
"""
|
||||
Reads the CSV file.
|
||||
"""
|
||||
files = os.listdir(data_path)
|
||||
if len(files) != 1:
|
||||
print("No files found in the directory")
|
||||
exit(1)
|
||||
csv_file_path = data_path + os.sep + files[0]
|
||||
return pd.read_csv(csv_file_path)
|
||||
|
||||
# Serialize the trained model to a byte buffer
|
||||
model_buffer = io.BytesIO()
|
||||
joblib.dump(log_reg, model_buffer)
|
||||
def compute(self):
|
||||
"""
|
||||
Trains a logistic regression model.
|
||||
"""
|
||||
iris = self._read_csv(DATA_DIR)
|
||||
|
||||
# Get the serialized model as a bytes object
|
||||
model_bytes = model_buffer.getvalue()
|
||||
# Droping the Species since we only need the measurements
|
||||
X = iris.drop(["Species"], axis=1)
|
||||
|
||||
# Define the path for the Unix domain socket
|
||||
socket_path = sys.argv[1]
|
||||
# converting into numpy array and assigning petal length and petal width
|
||||
X = X.to_numpy()[:, (3, 4)]
|
||||
y = iris["Species"]
|
||||
|
||||
# Create a Unix domain socket client
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
X_train, _, y_train, _ = train_test_split(X, y, test_size=0.5, random_state=42)
|
||||
|
||||
try:
|
||||
# Connect to the server
|
||||
client.connect(socket_path)
|
||||
log_reg = LogisticRegression()
|
||||
log_reg.fit(X_train, y_train)
|
||||
self.model = log_reg
|
||||
|
||||
# Send the serialized model over the socket
|
||||
client.send(model_bytes)
|
||||
def save_result(self):
|
||||
"""
|
||||
Sends the result to a file.
|
||||
"""
|
||||
try:
|
||||
os.makedirs(RESULTS_DIR)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
finally:
|
||||
# Close the socket
|
||||
client.close()
|
||||
results_file = RESULTS_DIR + os.sep + RESULTS_FILE
|
||||
joblib.dump(self.model, results_file)
|
||||
|
||||
def read_results_from_file(self, results_file):
|
||||
"""
|
||||
Reads the results from a file.
|
||||
"""
|
||||
if results_file.endswith(".zip"):
|
||||
try:
|
||||
os.makedirs(RESULTS_DIR)
|
||||
except FileExistsError:
|
||||
pass
|
||||
with zipfile.ZipFile(results_file, "r") as zip_ref:
|
||||
zip_ref.extractall(RESULTS_DIR)
|
||||
self.model = joblib.load(RESULTS_DIR + os.sep + RESULTS_FILE)
|
||||
else:
|
||||
self.model = joblib.load(results_file)
|
||||
|
||||
def predict(self, data_path=""):
|
||||
iris = self._read_csv(data_path)
|
||||
|
||||
# Droping the Species since we only need the measurements
|
||||
X = iris.drop(["Species"], axis=1)
|
||||
|
||||
# converting into numpy array and assigning petal length and petal width
|
||||
X = X.to_numpy()[:, (3, 4)]
|
||||
y = iris["Species"]
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
X, y, test_size=0.5, random_state=42
|
||||
)
|
||||
|
||||
training_prediction = self.model.predict(X_train)
|
||||
test_prediction = self.model.predict(X_test)
|
||||
|
||||
print("Precision, Recall, Confusion matrix, in training\n")
|
||||
print(metrics.classification_report(y_train, training_prediction, digits=3))
|
||||
print(metrics.confusion_matrix(y_train, training_prediction))
|
||||
print("Precision, Recall, Confusion matrix, in testing\n")
|
||||
print(metrics.classification_report(y_test, test_prediction, digits=3))
|
||||
print(metrics.confusion_matrix(y_test, test_prediction))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
computation = Computation()
|
||||
if len(sys.argv) == 1:
|
||||
computation.compute()
|
||||
computation.save_result()
|
||||
elif len(sys.argv) == 4 and sys.argv[1] == "predict":
|
||||
computation.read_results_from_file(sys.argv[2])
|
||||
computation.predict(sys.argv[3])
|
||||
else:
|
||||
print("Invalid arguments")
|
||||
exit(1)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import pandas as pd
|
||||
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn import metrics
|
||||
import joblib
|
||||
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
csv_file_path = sys.argv[1]
|
||||
model_filename = sys.argv[2]
|
||||
|
||||
# Load the CSV file into a Pandas DataFrame
|
||||
iris = pd.read_csv(csv_file_path)
|
||||
|
||||
log_reg = joblib.load(model_filename)
|
||||
|
||||
# Now you have the Iris dataset loaded into the iris_df DataFrame
|
||||
print(iris.head()) # Display the first few rows of the DataFrame
|
||||
|
||||
# Droping the Species since we only need the measurements
|
||||
X = iris.drop(['Species'], axis=1)
|
||||
|
||||
# converting into numpy array and assigning petal length and petal width
|
||||
X = X.to_numpy()[:, (3,4)]
|
||||
y = iris['Species']
|
||||
|
||||
# Splitting into train and test
|
||||
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.5, random_state=42)
|
||||
|
||||
training_prediction = log_reg.predict(X_train)
|
||||
test_prediction = log_reg.predict(X_test)
|
||||
|
||||
print("Precision, Recall, Confusion matrix, in training\n")
|
||||
|
||||
# Precision Recall scores
|
||||
print(metrics.classification_report(y_train, training_prediction, digits=3))
|
||||
|
||||
# Confusion matrix
|
||||
print(metrics.confusion_matrix(y_train, training_prediction))
|
||||
|
||||
print("Precision, Recall, Confusion matrix, in testing\n")
|
||||
|
||||
# Precision Recall scores
|
||||
print(metrics.classification_report(y_test, test_prediction, digits=3))
|
||||
|
||||
# Confusion matrix
|
||||
print(metrics.confusion_matrix(y_test, test_prediction))
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"snp_policy": {
|
||||
"minimum_guest_svn": 0,
|
||||
"policy": 196608,
|
||||
"minimum_tcb": 1,
|
||||
"minimum_version": "1.0",
|
||||
"minimum_launch_tcb": 1,
|
||||
"measurement": [232, 141, 188, 114, 162, 221, 214, 6, 150, 248, 3, 173, 230, 39, 48, 120, 105, 243, 15, 242, 79, 67, 112, 128, 44, 119, 216, 226, 170, 255, 212, 154, 58, 68, 231, 30, 20, 235, 228, 42, 43, 1, 95, 191, 51, 113, 19, 72],
|
||||
"minimum_build": 1
|
||||
},
|
||||
"root_of_trust": {
|
||||
"product": "Milan",
|
||||
"check_crl": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user