feat: edge node unit tests and related updates
- Add edge node unit tests and transport package - Add node test client and related artifacts - Update bootstrap, node, and config modules - Add proto generated files - Update Makefile and configuration files
This commit is contained in:
parent
173d2586e8
commit
c46874055a
34 changed files with 4666 additions and 1081 deletions
2
Makefile
2
Makefile
|
|
@ -20,7 +20,7 @@ test:
|
|||
proto:
|
||||
protoc \
|
||||
--go_out=. \
|
||||
--go_opt=paths=source_relative \
|
||||
--go_opt=module=iop \
|
||||
--proto_path=. \
|
||||
proto/iop/runtime.proto \
|
||||
proto/iop/node.proto \
|
||||
|
|
|
|||
200
agent-task/edge_node_arch/code_review_0.log
Normal file
200
agent-task/edge_node_arch/code_review_0.log
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
<!-- task=edge_node_arch plan=1 tag=REFACTOR -->
|
||||
|
||||
# Code Review Reference - REFACTOR
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=edge_node_arch, plan=1, tag=REFACTOR
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW.md` → `code_review_1.log`
|
||||
2. `PLAN.md` → `plan_1.log`
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REFACTOR-1] proto 컴파일 및 go.mod 업데이트 | [x] |
|
||||
| [REFACTOR-2] node transport 클라이언트 계층 구현 | [x] |
|
||||
| [REFACTOR-3] node.go proto 타입으로 교체 | [x] |
|
||||
| [REFACTOR-4] node bootstrap DialEdge 연결 추가 | [x] |
|
||||
| [REFACTOR-5] edge TCP 서버 + NodeRegistry 구현 | [x] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- `make`가 환경에 설치되어 있지 않아 `make proto`는 실패했다. 동일한 Makefile 명령을 `protoc --go_out=. --go_opt=module=iop --proto_path=.`로 직접 실행해 `proto/gen/iop/*.pb.go`를 생성했다.
|
||||
- 기존 Makefile의 `paths=source_relative` 옵션은 `proto/iop/*.pb.go`를 생성하므로, PLAN의 기대 경로(`proto/gen/iop`)와 import path(`iop/proto/gen/iop`)를 맞추기 위해 `--go_opt=module=iop`로 변경했다.
|
||||
- CODE_REVIEW의 체크포인트 중 `RunRequest.Input`을 bytes/json으로 확인하라는 항목은 현재 `proto/iop/runtime.proto` 정의와 PLAN의 본문이 `google.protobuf.Struct`를 사용하므로 `AsMap()` 변환 기준으로 구현했다.
|
||||
- `edgeParserMap`에는 `SendRequestTyped` 응답 파싱을 위해 `CapabilityResponse` 파서를 추가했다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- node는 proto-socket `TcpClient`를 `transport.Session`으로 감싸고, `RunRequest`, `CapabilityRequest`, `CancelRequest` 리스너를 세션 생성 시 등록한다.
|
||||
- edge는 proto-socket `TcpServer`로 node 연결을 받고, 연결 직후 `CapabilityRequest`를 보내 `NodeRegistry`에 nodeID와 adapter 목록을 등록한다.
|
||||
- `RunEvent`는 삭제된 JSON Envelope 없이 protobuf 메시지를 proto-socket으로 직접 전송한다.
|
||||
- `RunRequest.Policy`와 `RunRequest.Input`은 nil-safe helper로 `google.protobuf.Struct.AsMap()` 변환한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `go.mod`에 `replace git.toki-labs.com/toki/common-proto-socket/go => ../proto-socket/go` 존재 여부
|
||||
- `proto/gen/iop/` 하위 4개 파일 생성 여부 (runtime, node, control, job)
|
||||
- `transport/session.go`의 `Handler` 인터페이스가 `*iop.RunRequest`, `*iop.CapabilityResponse`, `*iop.CancelRequest` 사용 여부
|
||||
- `transport/session.go`의 `newSession`에서 `AddListenerTyped`(RunRequest, CancelRequest), `AddRequestListenerTyped`(CapabilityRequest) 세 리스너 모두 등록 여부
|
||||
- `transport/parser.go`의 `nodeParserMap`에 RunRequest, CancelRequest, CapabilityRequest 세 타입 등록 여부
|
||||
- `node.go`의 `sessionSink.Emit`이 `*iop.RunEvent`를 `sess.Send`로 직접 전송하는지 (Envelope 없음)
|
||||
- `node.go`의 `OnRunRequest`에서 `req.GetPolicy()` / `req.GetInput()` Struct를 `AsMap()`으로 `map[string]any` 변환 여부
|
||||
- `bootstrap/module.go`에 `transport.DialEdge` 호출이 fx OnStart에 있고 OnStop에서 `sess.Close()` 호출 여부
|
||||
- `apps/edge/internal/node/registry.go`의 NodeRegistry가 Register/Unregister/Pick/Count/All 메서드 모두 구현 여부
|
||||
- edge `onNodeConnected`에서 CapabilityRequest 전송 후 nodeID를 받아 Registry에 등록하는 흐름 확인
|
||||
- edge `onNodeConnected`에서 disconnect 시 Registry.Unregister 호출 여부
|
||||
- `registry_test.go` 테스트 3개 통과 여부
|
||||
- 최종 검증: edge 실행 후 node 연결 시 edge 로그에 `node registered` 출력 확인
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### REFACTOR-1 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/bin:$PATH make proto
|
||||
/usr/bin/bash: line 1: make: command not found
|
||||
|
||||
$ PATH=/config/go-sdk/bin:$PATH protoc --go_out=. --go_opt=module=iop --proto_path=. proto/iop/runtime.proto proto/iop/node.proto proto/iop/control.proto proto/iop/job.proto
|
||||
(no output)
|
||||
|
||||
$ ls proto/gen/iop/
|
||||
control.pb.go
|
||||
job.pb.go
|
||||
node.pb.go
|
||||
runtime.pb.go
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go mod tidy
|
||||
(no output)
|
||||
```
|
||||
|
||||
### REFACTOR-2 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/node/internal/transport/...
|
||||
(no output)
|
||||
```
|
||||
|
||||
### REFACTOR-3 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/node/...
|
||||
(no output)
|
||||
```
|
||||
|
||||
### REFACTOR-4 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/node/...
|
||||
(no output)
|
||||
```
|
||||
|
||||
### REFACTOR-5 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/edge/...
|
||||
(no output)
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/edge/internal/node/...
|
||||
ok iop/apps/edge/internal/node 0.002s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
# 터미널 A
|
||||
$ ./bin/iop-edge serve --config configs/edge.yaml
|
||||
{"level":"info","caller":"transport/server.go:66","msg":"edge listening for nodes","addr":"0.0.0.0:9090"}
|
||||
{"level":"info","caller":"transport/server.go:75","msg":"node connection established"}
|
||||
{"level":"info","caller":"transport/server.go:102","msg":"node registered","node_id":"node-001","adapters":1}
|
||||
|
||||
# 터미널 B
|
||||
$ ./bin/iop-node serve --config configs/node.yaml
|
||||
{"level":"info","caller":"store/store.go:53","msg":"store ready","dsn":"file:iop.db?cache=shared&mode=rwc"}
|
||||
{"level":"info","caller":"transport/client.go:35","msg":"connected to edge","addr":"localhost:9090","node_id":"node-001"}
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./...
|
||||
(no output)
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./...
|
||||
? iop/apps/control-plane/cmd/iop-control-plane [no test files]
|
||||
? iop/apps/edge/cmd/iop-edge [no test files]
|
||||
? iop/apps/edge/internal/bootstrap [no test files]
|
||||
ok iop/apps/edge/internal/node (cached)
|
||||
? iop/apps/edge/internal/transport [no test files]
|
||||
? iop/apps/node/cmd/iop-node [no test files]
|
||||
? iop/apps/node/internal/adapters [no test files]
|
||||
? iop/apps/node/internal/adapters/cli [no test files]
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
? iop/apps/node/internal/adapters/ollama [no test files]
|
||||
? iop/apps/node/internal/adapters/vllm [no test files]
|
||||
? iop/apps/node/internal/bootstrap [no test files]
|
||||
? iop/apps/node/internal/node [no test files]
|
||||
? iop/apps/node/internal/router [no test files]
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
? iop/apps/node/internal/store [no test files]
|
||||
? iop/apps/node/internal/transport [no test files]
|
||||
? iop/apps/worker/cmd/iop-worker [no test files]
|
||||
? iop/packages/auth [no test files]
|
||||
? iop/packages/config [no test files]
|
||||
? iop/packages/jobs [no test files]
|
||||
? iop/packages/metadata [no test files]
|
||||
? iop/packages/observability [no test files]
|
||||
? iop/packages/policy [no test files]
|
||||
? iop/packages/version [no test files]
|
||||
? iop/proto/gen/iop [no test files]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 종합 판정: WARN
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 판정 |
|
||||
|------|------|
|
||||
| Correctness | Warn |
|
||||
| Completeness | Pass |
|
||||
| Test coverage | Pass |
|
||||
| API contract | Pass |
|
||||
| Code quality | Pass |
|
||||
| Plan deviation | Pass |
|
||||
| Verification trust | Pass |
|
||||
|
||||
### 발견된 문제
|
||||
|
||||
- **Suggested** `apps/edge/internal/transport/server.go:101-107`
|
||||
`client.AddDisconnectListener` 등록이 `s.registry.Register(entry)` 이후에 위치함.
|
||||
CapabilityResponse 수신 후 node가 즉시 disconnect되면 disconnect 이벤트가 listener 등록 전에 발생해 registry stale entry가 남을 수 있음.
|
||||
Fix: `AddDisconnectListener` 블록을 `s.registry.Register(entry)` 이전으로 이동.
|
||||
```go
|
||||
// before:
|
||||
s.registry.Register(entry)
|
||||
s.logger.Info("node registered", ...)
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) { ... })
|
||||
|
||||
// after:
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) {
|
||||
s.registry.Unregister(entry.NodeID)
|
||||
s.logger.Info("node unregistered", zap.String("node_id", entry.NodeID))
|
||||
})
|
||||
s.registry.Register(entry)
|
||||
s.logger.Info("node registered", ...)
|
||||
```
|
||||
|
||||
- **Nit** `apps/node/internal/node/node.go:111`
|
||||
`a.Capabilities(context.Background())`가 `OnCapabilityRequest`에 전달된 `ctx`를 무시함.
|
||||
`ctx`를 직접 전달하면 호출 체인에서 cancellation이 일관성 있게 전파됨.
|
||||
|
||||
### 다음 단계
|
||||
|
||||
WARN: Suggested 이슈 수정을 위한 새 PLAN.md와 CODE_REVIEW.md 스텁을 작성한 후 구현 루프를 계속한다.
|
||||
110
agent-task/edge_node_arch/code_review_1.log
Normal file
110
agent-task/edge_node_arch/code_review_1.log
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<!-- task=edge_node_arch plan=1 tag=REVIEW_REFACTOR -->
|
||||
|
||||
# Code Review Reference - REVIEW_REFACTOR
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=edge_node_arch, plan=1, tag=REVIEW_REFACTOR
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW.md` → `code_review_1.log` (N = 기존 code_review_*.log 수)
|
||||
2. `PLAN.md` → `plan_1.log` (M = 기존 plan_*.log 수)
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_REFACTOR-1] onNodeConnected disconnect 리스너 순서 수정 | [x] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `CapabilityResponse`를 받아 `NodeEntry`를 만든 직후 disconnect listener를 먼저 등록하고, 그 다음 registry에 등록한다.
|
||||
- listener는 기존과 동일하게 해당 nodeID를 registry에서 제거하고 `node unregistered` 로그를 남긴다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `apps/edge/internal/transport/server.go`의 `onNodeConnected` 고루틴에서 `client.AddDisconnectListener(...)` 블록이 `s.registry.Register(entry)` 이전에 위치하는지 확인
|
||||
- `go build ./apps/edge/...`가 오류 없이 통과하는지 확인
|
||||
- `go build ./...`와 `go test ./...`가 모두 통과하는지 확인
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### REVIEW_REFACTOR-1 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/edge/...
|
||||
(no output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./...
|
||||
(no output)
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./...
|
||||
? iop/apps/control-plane/cmd/iop-control-plane [no test files]
|
||||
? iop/apps/edge/cmd/iop-edge [no test files]
|
||||
? iop/apps/edge/internal/bootstrap [no test files]
|
||||
ok iop/apps/edge/internal/node (cached)
|
||||
? iop/apps/edge/internal/transport [no test files]
|
||||
? iop/apps/node/cmd/iop-node [no test files]
|
||||
? iop/apps/node/internal/adapters [no test files]
|
||||
? iop/apps/node/internal/adapters/cli [no test files]
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
? iop/apps/node/internal/adapters/ollama [no test files]
|
||||
? iop/apps/node/internal/adapters/vllm [no test files]
|
||||
? iop/apps/node/internal/bootstrap [no test files]
|
||||
? iop/apps/node/internal/node [no test files]
|
||||
? iop/apps/node/internal/router [no test files]
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
? iop/apps/node/internal/store [no test files]
|
||||
? iop/apps/node/internal/transport [no test files]
|
||||
? iop/apps/worker/cmd/iop-worker [no test files]
|
||||
? iop/packages/auth [no test files]
|
||||
? iop/packages/config [no test files]
|
||||
? iop/packages/jobs [no test files]
|
||||
? iop/packages/metadata [no test files]
|
||||
? iop/packages/observability [no test files]
|
||||
? iop/packages/policy [no test files]
|
||||
? iop/packages/version [no test files]
|
||||
? iop/proto/gen/iop [no test files]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 종합 판정: PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 판정 |
|
||||
|------|------|
|
||||
| Correctness | Pass |
|
||||
| Completeness | Pass |
|
||||
| Test coverage | Pass |
|
||||
| API contract | Pass |
|
||||
| Code quality | Pass |
|
||||
| Plan deviation | Pass |
|
||||
| Verification trust | Pass |
|
||||
|
||||
### 발견된 문제
|
||||
|
||||
없음. `server.go:101-106` 기준으로 `AddDisconnectListener`가 `registry.Register` 이전에 위치함이 확인됨. race window 제거.
|
||||
|
||||
### 다음 단계
|
||||
|
||||
PASS: `complete.log` 작성 후 종료.
|
||||
33
agent-task/edge_node_arch/complete.log
Normal file
33
agent-task/edge_node_arch/complete.log
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Task Complete — edge_node_arch
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-05-02
|
||||
|
||||
## 요약
|
||||
|
||||
edge를 TCP 서버로, node를 TCP 클라이언트로 재설계. proto-socket + protobuf 기반 통신 구현. 2 루프(REFACTOR → REVIEW_REFACTOR).
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Code Review | 판정 |
|
||||
|------|-------------|------|
|
||||
| plan_0.log (REFACTOR) | code_review_0.log | WARN |
|
||||
| plan_1.log (REVIEW_REFACTOR) | code_review_1.log | PASS |
|
||||
|
||||
## 최종 리뷰 요약
|
||||
|
||||
- `proto/gen/iop/` 4개 파일 생성 (runtime, node, control, job)
|
||||
- `apps/node/internal/transport/`: `client.go`, `parser.go`, `session.go` — proto-socket `TcpClient` 기반 node→edge 클라이언트
|
||||
- `apps/node/internal/node/node.go`: proto 타입 사용, `structAsMap` helper, `sessionSink.Emit`이 `*iop.RunEvent` 직접 전송
|
||||
- `apps/node/internal/bootstrap/module.go`: `transport.DialEdge` OnStart, `sess.Close()` OnStop
|
||||
- `apps/edge/internal/node/registry.go`: `NodeRegistry` — Register/Unregister/Pick/Count/All
|
||||
- `apps/edge/internal/transport/server.go`: proto-socket `TcpServer`, `onNodeConnected` → CapabilityRequest → Register
|
||||
- `apps/edge/internal/bootstrap/module.go`: fx 모듈 wiring
|
||||
- `packages/config/config.go`: `EdgeConfig`, `LoadEdge`, `TransportConf.EdgeAddr`
|
||||
- `go.mod`: proto-socket require + replace
|
||||
- `configs/edge.yaml` 추가
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- `apps/node/internal/node/node.go:111` `a.Capabilities(context.Background())` — 전달받은 `ctx` 대신 `context.Background()` 사용. 기능 영향 없음.
|
||||
1096
agent-task/edge_node_arch/plan_0.log
Normal file
1096
agent-task/edge_node_arch/plan_0.log
Normal file
File diff suppressed because it is too large
Load diff
79
agent-task/edge_node_arch/plan_1.log
Normal file
79
agent-task/edge_node_arch/plan_1.log
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<!-- task=edge_node_arch plan=1 tag=REVIEW_REFACTOR -->
|
||||
|
||||
# Plan - REVIEW_REFACTOR
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=edge_node_arch, plan=1, tag=REVIEW_REFACTOR
|
||||
parent=REFACTOR (code_review_0.log)
|
||||
|
||||
## 배경
|
||||
|
||||
REFACTOR 코드리뷰에서 발견된 Suggested 이슈 수정.
|
||||
|
||||
---
|
||||
|
||||
## [REVIEW_REFACTOR-1] onNodeConnected disconnect 리스너 순서 수정
|
||||
|
||||
### 문제
|
||||
|
||||
`apps/edge/internal/transport/server.go`의 `onNodeConnected` 고루틴에서
|
||||
`s.registry.Register(entry)` 이후에 `client.AddDisconnectListener`가 등록됨.
|
||||
두 호출 사이에 node가 disconnect되면 이벤트가 리스너 등록 전에 발생해
|
||||
registry에 stale entry가 영구 잔존할 수 있음.
|
||||
|
||||
### 해결
|
||||
|
||||
`AddDisconnectListener` 블록을 `registry.Register` 이전으로 이동.
|
||||
|
||||
Before (`server.go:101-110`):
|
||||
```go
|
||||
s.registry.Register(entry)
|
||||
s.logger.Info("node registered",
|
||||
zap.String("node_id", entry.NodeID),
|
||||
zap.Int("adapters", len(entry.Adapters)),
|
||||
)
|
||||
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) {
|
||||
s.registry.Unregister(entry.NodeID)
|
||||
s.logger.Info("node unregistered", zap.String("node_id", entry.NodeID))
|
||||
})
|
||||
```
|
||||
|
||||
After:
|
||||
```go
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) {
|
||||
s.registry.Unregister(entry.NodeID)
|
||||
s.logger.Info("node unregistered", zap.String("node_id", entry.NodeID))
|
||||
})
|
||||
|
||||
s.registry.Register(entry)
|
||||
s.logger.Info("node registered",
|
||||
zap.String("node_id", entry.NodeID),
|
||||
zap.Int("adapters", len(entry.Adapters)),
|
||||
)
|
||||
```
|
||||
|
||||
### 체크리스트
|
||||
|
||||
- [x] `server.go`에서 `AddDisconnectListener` 블록이 `registry.Register` 이전에 위치
|
||||
|
||||
### 테스트 결정
|
||||
|
||||
기존 registry_test.go는 Register/Unregister 순서를 직접 테스트하지 않으므로 코드 변경만으로 충분.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./apps/edge/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go build ./...
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./...
|
||||
```
|
||||
122
agent-task/edge_node_unit_tests/CODE_REVIEW.md
Normal file
122
agent-task/edge_node_unit_tests/CODE_REVIEW.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<!-- task=edge_node_unit_tests plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=edge_node_unit_tests, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW.md` → `code_review_0.log` (N = 기존 code_review_*.log 수)
|
||||
2. `PLAN.md` → `plan_0.log` (M = 기존 plan_*.log 수)
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] apps/node/internal/transport/parser_test.go | [x] |
|
||||
| [TEST-2] apps/node/internal/transport/session_test.go | [x] |
|
||||
| [TEST-3] apps/node/internal/node/node_test.go | [x] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- 계획대로 `apps/node/internal/transport/parser_test.go`, `apps/node/internal/transport/session_test.go`, `apps/node/internal/node/node_test.go`를 신규 작성했다.
|
||||
- `parser_test.go`의 RunRequest 검증은 계획의 RunId·Adapter에 더해 Model도 함께 확인했다.
|
||||
- `node_test.go`의 `fixedRouter`는 요청의 Model·Workspace·Policy·Input·TimeoutSec·Metadata를 `ExecutionSpec`으로 전달하도록 작성했다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `parser_test.go`는 `package transport`를 사용해 unexported `nodeParserMap()`을 직접 검증했다.
|
||||
- `session_test.go`는 `&transport.Session{}` 제로값을 사용해 TcpClient 없이 cancel 등록·호출·해제 흐름만 검증했다.
|
||||
- `node_test.go`는 `sink.Emit`을 호출하지 않는 `noEmitAdapter`와 in-memory SQLite store를 사용해 외부 연결 없이 Node 핵심 경로를 검증했다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `parser_test.go`가 `package transport`로 선언되어 `nodeParserMap()` 접근 가능한지
|
||||
- 3개 메시지 타입(RunRequest, CancelRequest, CapabilityRequest) 파서 round-trip 검증 여부
|
||||
- `session_test.go`에서 `&transport.Session{}` 제로값 사용 (TcpClient nil) 확인
|
||||
- `TestSession_RegisterCancel_CancelRun`: cancel 호출 여부 확인
|
||||
- `TestSession_DeregisterCancel`: 해제 후 미호출 확인
|
||||
- `node_test.go`의 `noEmitAdapter`가 `sink.Emit`을 호출하지 않는지 확인 (TcpClient nil 안전)
|
||||
- `makeNode`에서 `:memory:` SQLite 사용 여부
|
||||
- `TestOnCapabilityRequest`: NodeId·AdapterName·Models 필드 검증
|
||||
- `TestOnRunRequest_RouterError`: 에러 메시지에 `"node: resolve:"` 접두사 포함
|
||||
- `TestOnRunRequest_AdapterNotFound`: 에러 메시지에 `"not found after routing"` 포함
|
||||
- `TestOnRunRequest_Success`: nil 반환 확인
|
||||
- `TestOnCancel_CallsCancelFn`: cancel 함수 호출 확인
|
||||
- `go test ./apps/node/...` 전체 pass
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/transport/...
|
||||
ok iop/apps/node/internal/transport 0.002s
|
||||
```
|
||||
|
||||
### TEST-2 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/transport/...
|
||||
ok iop/apps/node/internal/transport 0.002s
|
||||
```
|
||||
|
||||
### TEST-3 중간 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/node/...
|
||||
ok iop/apps/node/internal/node 0.004s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/...
|
||||
? iop/apps/node/cmd/iop-node [no test files]
|
||||
? iop/apps/node/internal/adapters [no test files]
|
||||
? iop/apps/node/internal/adapters/cli [no test files]
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
? iop/apps/node/internal/adapters/ollama [no test files]
|
||||
? iop/apps/node/internal/adapters/vllm [no test files]
|
||||
? iop/apps/node/internal/bootstrap [no test files]
|
||||
ok iop/apps/node/internal/node (cached)
|
||||
? iop/apps/node/internal/router [no test files]
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
? iop/apps/node/internal/store [no test files]
|
||||
ok iop/apps/node/internal/transport (cached)
|
||||
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./...
|
||||
? iop/apps/control-plane/cmd/iop-control-plane [no test files]
|
||||
? iop/apps/edge/cmd/iop-edge [no test files]
|
||||
? iop/apps/edge/internal/bootstrap [no test files]
|
||||
ok iop/apps/edge/internal/node (cached)
|
||||
? iop/apps/edge/internal/transport [no test files]
|
||||
? iop/apps/node/cmd/iop-node [no test files]
|
||||
? iop/apps/node/internal/adapters [no test files]
|
||||
? iop/apps/node/internal/adapters/cli [no test files]
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
? iop/apps/node/internal/adapters/ollama [no test files]
|
||||
? iop/apps/node/internal/adapters/vllm [no test files]
|
||||
? iop/apps/node/internal/bootstrap [no test files]
|
||||
ok iop/apps/node/internal/node (cached)
|
||||
? iop/apps/node/internal/router [no test files]
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
? iop/apps/node/internal/store [no test files]
|
||||
ok iop/apps/node/internal/transport (cached)
|
||||
? iop/apps/worker/cmd/iop-worker [no test files]
|
||||
? iop/packages/auth [no test files]
|
||||
? iop/packages/config [no test files]
|
||||
? iop/packages/jobs [no test files]
|
||||
? iop/packages/metadata [no test files]
|
||||
? iop/packages/observability [no test files]
|
||||
? iop/packages/policy [no test files]
|
||||
? iop/packages/version [no test files]
|
||||
? iop/proto/gen/iop [no test files]
|
||||
```
|
||||
268
agent-task/edge_node_unit_tests/PLAN.md
Normal file
268
agent-task/edge_node_unit_tests/PLAN.md
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
<!-- task=edge_node_unit_tests plan=0 tag=TEST -->
|
||||
|
||||
# Plan - TEST
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
각 항목의 체크리스트를 완료하고, 중간 검증과 최종 검증 명령을 실행한 뒤 출력을 `CODE_REVIEW.md`의 검증 결과 섹션에 붙여 넣는다. `CODE_REVIEW.md`의 모든 섹션(계획 대비 변경 사항, 주요 설계 결정, 검증 결과)을 실제 내용으로 채운다.
|
||||
|
||||
## 배경
|
||||
|
||||
`edge_node_arch` 작업으로 구현된 transport 계층과 node 핵심 로직에 유닛 테스트가 없다. `apps/edge/internal/node/registry_test.go` 3개만 존재한다. parser의 역직렬화 정확성, Session의 cancel 관리, Node의 핵심 메서드(OnCapabilityRequest·OnRunRequest·OnCancel) 경로가 검증되지 않은 상태다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
TEST-1 → TEST-2 → TEST-3 순으로 작성한다. TEST-3은 `*transport.Session` 제로값을 사용하므로 transport 패키지 변경이 없다면 순서 무관하게 컴파일된다.
|
||||
|
||||
---
|
||||
|
||||
## [TEST-1] apps/node/internal/transport/parser_test.go
|
||||
|
||||
### 문제
|
||||
|
||||
`parser.go`의 `nodeParserMap()`(line 10)이 3개의 메시지 타입(RunRequest, CancelRequest, CapabilityRequest)을 파싱하는데 역직렬화 정확성을 검증하는 테스트가 없다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
각 파서 함수를 직접 호출해 proto.Marshal → parser → 필드 비교로 round-trip을 검증한다. `nodeParserMap()`은 unexported이므로 `package transport`(동일 패키지) 테스트로 작성한다.
|
||||
|
||||
```go
|
||||
// apps/node/internal/transport/parser_test.go
|
||||
package transport
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
func TestNodeParserMap_RunRequest(t *testing.T) {
|
||||
m := nodeParserMap()
|
||||
original := &iop.RunRequest{RunId: "r1", Adapter: "mock", Model: "v1"}
|
||||
b, _ := proto.Marshal(original)
|
||||
|
||||
key := toki.TypeNameOf(original)
|
||||
parsed, err := m[key](b)
|
||||
if err != nil {
|
||||
t.Fatalf("parse error: %v", err)
|
||||
}
|
||||
got := parsed.(*iop.RunRequest)
|
||||
if got.GetRunId() != "r1" || got.GetAdapter() != "mock" {
|
||||
t.Errorf("unexpected: %+v", got)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
동일 패턴으로 CancelRequest, CapabilityRequest도 작성.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/node/internal/transport/parser_test.go` 신규 생성
|
||||
- [ ] `package transport` 선언
|
||||
- [ ] `TestNodeParserMap_RunRequest`: RunId·Adapter 필드 검증
|
||||
- [ ] `TestNodeParserMap_CancelRequest`: RunId 필드 검증
|
||||
- [ ] `TestNodeParserMap_CapabilityRequest`: 역직렬화 오류 없음 검증
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
신규 작성. parser는 새 공개 API가 아니지만 proto 역직렬화 로직의 정확성을 보장하는 회귀 방지 테스트로 필요하다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/transport/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [TEST-2] apps/node/internal/transport/session_test.go
|
||||
|
||||
### 문제
|
||||
|
||||
`session.go`의 cancel 관리 메서드 `RegisterCancel`(line 78), `CancelRun`(line 88), `DeregisterCancel`(line 83)에 테스트가 없다. `*transport.Session` 제로값(`&Session{}`)은 `cancelFns sync.Map`이 유효한 제로값이므로 실제 TcpClient 없이 테스트 가능하다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`&transport.Session{}`을 직접 생성해 cancel 함수 등록 → 호출 → 해제 흐름을 검증한다. `package transport_test`로 작성한다.
|
||||
|
||||
```go
|
||||
// apps/node/internal/transport/session_test.go
|
||||
package transport_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"iop/apps/node/internal/transport"
|
||||
)
|
||||
|
||||
func TestSession_RegisterCancel_CancelRun(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
called := false
|
||||
sess.RegisterCancel("run-1", func() { called = true })
|
||||
sess.CancelRun("run-1")
|
||||
if !called {
|
||||
t.Fatal("cancel function was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_DeregisterCancel(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
called := false
|
||||
sess.RegisterCancel("run-1", func() { called = true })
|
||||
sess.DeregisterCancel("run-1")
|
||||
sess.CancelRun("run-1")
|
||||
if called {
|
||||
t.Fatal("cancel function should not have been called after deregister")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_CancelRun_UnknownID(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
// must not panic
|
||||
sess.CancelRun("nonexistent")
|
||||
}
|
||||
```
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/node/internal/transport/session_test.go` 신규 생성
|
||||
- [ ] `package transport_test` 선언
|
||||
- [ ] `TestSession_RegisterCancel_CancelRun`: cancel 함수 호출 확인
|
||||
- [ ] `TestSession_DeregisterCancel`: 해제 후 호출 안됨 확인
|
||||
- [ ] `TestSession_CancelRun_UnknownID`: panic 없음 확인
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
신규 작성. cancel 관리 로직은 `OnRunRequest`의 timeout 경로에서 사용되며, race condition 수정(edge_node_arch REVIEW_REFACTOR)과 유사한 동시성 로직이다. 회귀 방지를 위해 필요하다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/transport/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [TEST-3] apps/node/internal/node/node_test.go
|
||||
|
||||
### 문제
|
||||
|
||||
`node.go`의 `OnCapabilityRequest`(line 108), `OnRunRequest`(line 48), `OnCancel`(line 126)에 테스트가 없다.
|
||||
|
||||
**의존성 처리 전략:**
|
||||
|
||||
| 의존성 | 타입 | 처리 방법 |
|
||||
|--------|------|-----------|
|
||||
| `runtime.Router` | interface | 테스트 파일 내 인라인 mock |
|
||||
| `*adapters.Registry` | struct | 직접 생성 후 no-emit 어댑터 등록 |
|
||||
| `*store.Store` | struct (SQLite) | `store.New(":memory:", zap.NewNop())` |
|
||||
| `*transport.Session` | struct | `&transport.Session{}` 제로값 |
|
||||
|
||||
`OnRunRequest`에서 `sink.Emit` → `sess.Send` → `client.Send`로 이어지는 경로를 피하기 위해 `no-emit` 테스트 어댑터를 사용한다. 이 어댑터는 `sink.Emit`을 호출하지 않으므로 TcpClient가 nil이어도 안전하다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
```go
|
||||
// apps/node/internal/node/node_test.go
|
||||
package node_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"iop/apps/node/internal/adapters"
|
||||
"iop/apps/node/internal/node"
|
||||
"iop/apps/node/internal/runtime"
|
||||
"iop/apps/node/internal/store"
|
||||
"iop/apps/node/internal/transport"
|
||||
"iop/packages/config"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
// noEmitAdapter는 sink.Emit을 호출하지 않아 *transport.Session 제로값과 함께 사용 가능하다.
|
||||
type noEmitAdapter struct{}
|
||||
|
||||
func (a *noEmitAdapter) Name() string { return "test" }
|
||||
func (a *noEmitAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
|
||||
return runtime.Capabilities{AdapterName: "test", Models: []string{"v1"}, MaxConcurrency: 1}, nil
|
||||
}
|
||||
func (a *noEmitAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fixedRouter struct{ adapterName string }
|
||||
|
||||
func (r *fixedRouter) Resolve(_ context.Context, req runtime.RunRequest) (runtime.ExecutionSpec, error) {
|
||||
return runtime.ExecutionSpec{RunID: req.RunID, Adapter: r.adapterName}, nil
|
||||
}
|
||||
|
||||
type errorRouter struct{ err error }
|
||||
|
||||
func (r *errorRouter) Resolve(_ context.Context, _ runtime.RunRequest) (runtime.ExecutionSpec, error) {
|
||||
return runtime.ExecutionSpec{}, r.err
|
||||
}
|
||||
|
||||
func makeNode(t *testing.T, rtr runtime.Router, reg *adapters.Registry) *node.Node {
|
||||
t.Helper()
|
||||
st, err := store.New(":memory:", zap.NewNop())
|
||||
if err != nil {
|
||||
t.Fatalf("store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
cfg := &config.NodeConfig{Node: config.NodeInfo{ID: "test-node"}}
|
||||
return node.New(cfg, rtr, reg, st, zap.NewNop())
|
||||
}
|
||||
```
|
||||
|
||||
**TestOnCapabilityRequest**: mock adapter 1개 등록 후 응답의 nodeID·어댑터 이름·모델 검증
|
||||
**TestOnRunRequest_RouterError**: `errorRouter` 사용, 반환 에러가 "node: resolve:" 접두사를 포함하는지 검증
|
||||
**TestOnRunRequest_AdapterNotFound**: `fixedRouter`가 "missing"을 반환하고 registry에 없는 경우 에러 검증
|
||||
**TestOnRunRequest_Success**: `fixedRouter` + `noEmitAdapter` + `&transport.Session{}`, nil 반환 검증
|
||||
**TestOnCancel_CallsCancelFn**: `sess.RegisterCancel` 등록 후 `OnCancel` 호출 → cancel 함수 실행 확인
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/node/internal/node/node_test.go` 신규 생성
|
||||
- [ ] `package node_test` 선언
|
||||
- [ ] `noEmitAdapter` 인라인 정의 (Emit 미호출)
|
||||
- [ ] `fixedRouter`, `errorRouter` 인라인 정의
|
||||
- [ ] `makeNode` 헬퍼 (in-memory store 사용)
|
||||
- [ ] `TestOnCapabilityRequest`: NodeId·AdapterName·Models 검증
|
||||
- [ ] `TestOnRunRequest_RouterError`: 에러 접두사 `"node: resolve:"` 검증
|
||||
- [ ] `TestOnRunRequest_AdapterNotFound`: 에러 포함 `"not found after routing"` 검증
|
||||
- [ ] `TestOnRunRequest_Success`: nil 반환 검증
|
||||
- [ ] `TestOnCancel_CallsCancelFn`: cancel 함수 호출 확인
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
신규 작성. `OnRunRequest`의 error path는 bug fix 성격의 회귀 테스트이며, `OnCapabilityRequest`·`OnCancel`은 새 공개 API의 기본 경로 테스트다. in-memory SQLite를 사용해 외부 의존성 없이 실행 가능하다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/internal/node/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/node/internal/transport/parser_test.go` (신규) | TEST-1 |
|
||||
| `apps/node/internal/transport/session_test.go` (신규) | TEST-2 |
|
||||
| `apps/node/internal/node/node_test.go` (신규) | TEST-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./apps/node/...
|
||||
$ PATH=/config/go-sdk/go/bin:/config/go-sdk/bin:$PATH go test ./...
|
||||
```
|
||||
|
||||
모든 테스트 pass, 새로 추가된 3개 파일 외 변경 없음.
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
<!-- task=node_testclient plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=node_testclient, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW.md` → `code_review_0.log`
|
||||
2. `PLAN.md` → `plan_0.log`
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] iop-node-testclient 커맨드 생성 | [ ] |
|
||||
| [TEST-2] Makefile에 testclient 빌드 타겟 추가 | [ ] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `sendMsg` / `recvEnvelope` 의 프레임 포맷이 서버 `transport/frame.go` 와 동일한 `4-byte big-endian length | JSON(Envelope)` 인지 확인
|
||||
- `internal` 경로 패키지를 import하지 않고 `iop/packages/protocol` 만 사용하는지 확인
|
||||
- `-caps` 플래그 없이 실행해도 RunRequest → RunEvent 흐름이 정상 동작하는지 확인
|
||||
- 60s 타임아웃 이전에 complete/error 이벤트 수신 시 정상 종료하는지 확인
|
||||
- Makefile `build` 타겟에 `iop-node-testclient` 라인이 추가되었는지 확인
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```
|
||||
$ go build -o bin/iop-node-testclient ./apps/node/cmd/iop-node-testclient
|
||||
(output)
|
||||
```
|
||||
|
||||
### TEST-2 중간 검증
|
||||
```
|
||||
$ make build
|
||||
$ ls -la bin/
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
# 터미널 A
|
||||
$ ./bin/iop-node serve --config configs/node.yaml
|
||||
(output)
|
||||
|
||||
# 터미널 B
|
||||
$ ./bin/iop-node-testclient
|
||||
$ ./bin/iop-node-testclient -caps
|
||||
$ ./bin/iop-node-testclient -prompt "안녕하세요 IOP!"
|
||||
(output)
|
||||
|
||||
$ go build ./...
|
||||
(output)
|
||||
```
|
||||
|
|
@ -1,345 +0,0 @@
|
|||
<!-- task=node_testclient plan=0 tag=TEST -->
|
||||
|
||||
# iop-node-testclient — 통합 테스트 클라이언트
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
각 체크리스트 항목을 완료한 뒤 `[x]`로 표시하세요.
|
||||
중간 검증 명령을 실제로 실행하고 출력을 `CODE_REVIEW.md` 검증 결과 섹션에 붙여 넣으세요.
|
||||
계획과 다르게 구현한 부분은 반드시 `계획 대비 변경 사항`에 기록하세요.
|
||||
|
||||
## 배경
|
||||
|
||||
`iop-node serve` 가 실행 중일 때 실제 TCP 연결로 RunRequest를 전송하고
|
||||
스트리밍 RunEvent를 수신하는 클라이언트가 없다.
|
||||
unit test 만으로는 transport 계층의 end-to-end 동작을 확인할 수 없으므로,
|
||||
수동 및 자동 통합 검증에 쓸 수 있는 `iop-node-testclient` 커맨드를 추가한다.
|
||||
|
||||
---
|
||||
|
||||
### [TEST-1] iop-node-testclient 커맨드 생성
|
||||
|
||||
**문제**
|
||||
|
||||
`apps/node/cmd/` 아래에 테스트 클라이언트가 없다.
|
||||
현재 transport 계층
|
||||
([apps/node/internal/transport/frame.go](apps/node/internal/transport/frame.go),
|
||||
[apps/node/internal/transport/codec.go](apps/node/internal/transport/codec.go),
|
||||
[apps/node/internal/transport/session.go:128-161](apps/node/internal/transport/session.go))
|
||||
의 wire format은 `4-byte big-endian length | JSON(Envelope)` 이다.
|
||||
클라이언트도 동일 프레이밍을 구현해야 한다.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
`apps/node/cmd/iop-node-testclient/main.go` 를 신규 생성한다.
|
||||
이미 `iop/packages/protocol` 에 모든 wire 타입이 정의되어 있으므로
|
||||
프레임 읽기/쓰기는 `encoding/binary` + `encoding/json` 으로 직접 구현한다.
|
||||
(transport 내부 패키지 unexported 함수 재사용 불가 — `internal` 경로)
|
||||
|
||||
커맨드 흐름:
|
||||
|
||||
```
|
||||
1. -addr 플래그로 node 주소 수신 (기본 localhost:9090)
|
||||
2. TCP Dial
|
||||
3. (선택) -caps 플래그 시 CapabilityRequest 전송 → 응답 출력
|
||||
4. RunRequest 전송 (adapter=mock, model=mock-echo, prompt="hello iop!")
|
||||
5. RunEvent 스트림 수신 → 각 이벤트 출력
|
||||
6. complete 또는 error 이벤트 수신 시 종료
|
||||
```
|
||||
|
||||
전체 구현 코드:
|
||||
|
||||
```go
|
||||
// apps/node/cmd/iop-node-testclient/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"iop/packages/protocol"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr := flag.String("addr", "localhost:9090", "iop-node address")
|
||||
caps := flag.Bool("caps", false, "send CapabilityRequest before RunRequest")
|
||||
prompt := flag.String("prompt", "hello iop!", "prompt to send")
|
||||
flag.Parse()
|
||||
|
||||
if err := run(*addr, *caps, *prompt); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(addr string, sendCaps bool, prompt string) error {
|
||||
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial %s: %w", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
fmt.Printf("connected → %s\n", addr)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if sendCaps {
|
||||
if err := sendMsg(conn, protocol.TypeCapabilityRequest, "", protocol.CapabilityRequest{}); err != nil {
|
||||
return fmt.Errorf("send capability request: %w", err)
|
||||
}
|
||||
fmt.Println("→ CapabilityRequest sent")
|
||||
env, err := recvEnvelope(conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recv capability response: %w", err)
|
||||
}
|
||||
if env.Type == protocol.TypeCapabilityResponse {
|
||||
var resp protocol.CapabilityResponse
|
||||
_ = json.Unmarshal(env.Payload, &resp)
|
||||
fmt.Printf("← CapabilityResponse node_id=%q adapters=%d\n", resp.NodeID, len(resp.Adapters))
|
||||
for _, a := range resp.Adapters {
|
||||
fmt.Printf(" adapter=%q models=%v max_concurrency=%d\n", a.Name, a.Models, a.MaxConcurrency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runID := fmt.Sprintf("testclient-%d", time.Now().UnixNano())
|
||||
req := protocol.RunRequest{
|
||||
RunID: runID,
|
||||
Adapter: "mock",
|
||||
Model: "mock-echo",
|
||||
Input: map[string]any{"prompt": prompt},
|
||||
}
|
||||
if err := sendMsg(conn, protocol.TypeRunRequest, runID, req); err != nil {
|
||||
return fmt.Errorf("send run request: %w", err)
|
||||
}
|
||||
fmt.Printf("→ RunRequest sent (run_id=%s)\n", runID)
|
||||
|
||||
return receiveEvents(ctx, conn)
|
||||
}
|
||||
|
||||
func receiveEvents(ctx context.Context, conn net.Conn) error {
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return fmt.Errorf("context: %w", err)
|
||||
}
|
||||
|
||||
env, err := recvEnvelope(conn)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
fmt.Println("connection closed by server")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("recv: %w", err)
|
||||
}
|
||||
|
||||
switch env.Type {
|
||||
case protocol.TypeRunEvent:
|
||||
var e protocol.RunEvent
|
||||
_ = json.Unmarshal(env.Payload, &e)
|
||||
switch e.Type {
|
||||
case "start":
|
||||
fmt.Printf("← [start]\n")
|
||||
case "delta":
|
||||
fmt.Printf("%s", e.Delta)
|
||||
case "complete":
|
||||
fmt.Printf("\n← [complete] message=%q input_tokens=%d output_tokens=%d\n",
|
||||
e.Message,
|
||||
func() int {
|
||||
if e.Usage != nil {
|
||||
return e.Usage.InputTokens
|
||||
}
|
||||
return 0
|
||||
}(),
|
||||
func() int {
|
||||
if e.Usage != nil {
|
||||
return e.Usage.OutputTokens
|
||||
}
|
||||
return 0
|
||||
}(),
|
||||
)
|
||||
return nil
|
||||
case "error":
|
||||
return fmt.Errorf("run error: %s", e.Error)
|
||||
}
|
||||
|
||||
case protocol.TypeHeartbeat:
|
||||
// 무시
|
||||
|
||||
case protocol.TypeError:
|
||||
var e protocol.ErrorMsg
|
||||
_ = json.Unmarshal(env.Payload, &e)
|
||||
return fmt.Errorf("server error %s: %s", e.Code, e.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendMsg wraps payload in an Envelope and writes a length-prefixed frame.
|
||||
func sendMsg(conn net.Conn, msgType protocol.MessageType, runID string, payload any) error {
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env := protocol.Envelope{
|
||||
ProtocolVersion: protocol.ProtocolVersion,
|
||||
RequestID: fmt.Sprintf("req-%d", time.Now().UnixNano()),
|
||||
RunID: runID,
|
||||
Type: msgType,
|
||||
Payload: data,
|
||||
}
|
||||
envBytes, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(hdr, uint32(len(envBytes)))
|
||||
if _, err := conn.Write(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = conn.Write(envBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// recvEnvelope reads one length-prefixed frame and decodes the Envelope.
|
||||
func recvEnvelope(conn net.Conn) (*protocol.Envelope, error) {
|
||||
hdr := make([]byte, 4)
|
||||
if _, err := io.ReadFull(conn, hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(hdr)
|
||||
if length == 0 {
|
||||
return nil, fmt.Errorf("received empty frame")
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var env protocol.Envelope
|
||||
return &env, json.Unmarshal(buf, &env)
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `apps/node/cmd/iop-node-testclient/main.go` 신규 생성
|
||||
- [ ] `-addr` 플래그 (기본 `localhost:9090`)
|
||||
- [ ] `-caps` 플래그 (CapabilityRequest 선택 전송)
|
||||
- [ ] `-prompt` 플래그 (기본 `"hello iop!"`)
|
||||
- [ ] TCP Dial + 타임아웃 (5s)
|
||||
- [ ] CapabilityRequest 전송 및 응답 출력 (`-caps` 시)
|
||||
- [ ] RunRequest 전송 (mock adapter)
|
||||
- [ ] RunEvent 스트림 수신 루프
|
||||
- [ ] complete / error 시 정상 종료
|
||||
- [ ] 60s 전체 타임아웃
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
통합 테스트 클라이언트 자체는 수동/E2E 검증 도구이므로 별도 `_test.go` 파일은 작성하지 않는다.
|
||||
단, `sendMsg` / `recvEnvelope` 함수는 `frame.go` + `codec.go` 유닛 테스트(node_unit_tests 태스크)로 간접 검증된다.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
빌드 가능 여부 확인:
|
||||
|
||||
```bash
|
||||
go build -o bin/iop-node-testclient ./apps/node/cmd/iop-node-testclient
|
||||
```
|
||||
|
||||
기대 결과: 빌드 에러 없음
|
||||
|
||||
---
|
||||
|
||||
### [TEST-2] Makefile에 testclient 빌드 타겟 추가
|
||||
|
||||
**문제**
|
||||
|
||||
[Makefile:6-9](Makefile)의 `build` 타겟에 testclient가 없다.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
```makefile
|
||||
# Before (Makefile:7)
|
||||
go build $(GOFLAGS) -o bin/iop-node ./apps/node/cmd/iop-node
|
||||
|
||||
# After
|
||||
go build $(GOFLAGS) -o bin/iop-node ./apps/node/cmd/iop-node
|
||||
go build $(GOFLAGS) -o bin/iop-node-testclient ./apps/node/cmd/iop-node-testclient
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `Makefile` — `build` 타겟에 `iop-node-testclient` 라인 추가
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
Makefile 변경이므로 테스트 파일 불필요.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
```bash
|
||||
make build
|
||||
ls -la bin/
|
||||
```
|
||||
|
||||
기대 결과: `bin/iop-node-testclient` 파일 생성됨
|
||||
|
||||
---
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/node/cmd/iop-node-testclient/main.go` | TEST-1 |
|
||||
| `Makefile` | TEST-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
두 개의 터미널이 필요하다.
|
||||
|
||||
**터미널 A — node 서버 실행:**
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
go build -o bin/iop-node ./apps/node/cmd/iop-node
|
||||
./bin/iop-node serve --config configs/node.yaml
|
||||
```
|
||||
|
||||
기대 결과: `transport listening addr=0.0.0.0:9090` 로그 출력
|
||||
|
||||
**터미널 B — testclient 실행:**
|
||||
|
||||
```bash
|
||||
go build -o bin/iop-node-testclient ./apps/node/cmd/iop-node-testclient
|
||||
|
||||
# 기본 RunRequest
|
||||
./bin/iop-node-testclient
|
||||
|
||||
# CapabilityRequest 포함
|
||||
./bin/iop-node-testclient -caps
|
||||
|
||||
# 커스텀 프롬프트
|
||||
./bin/iop-node-testclient -prompt "안녕하세요 IOP!"
|
||||
```
|
||||
|
||||
기대 결과:
|
||||
|
||||
```
|
||||
connected → localhost:9090
|
||||
→ RunRequest sent (run_id=testclient-...)
|
||||
← [start]
|
||||
echo: hello iop!
|
||||
← [complete] message="mock execution complete" input_tokens=2 output_tokens=4
|
||||
```
|
||||
|
||||
**빌드 전체 확인:**
|
||||
|
||||
```bash
|
||||
go build ./...
|
||||
```
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<!-- task=node_unit_tests plan=0 tag=TEST -->
|
||||
|
||||
# Code Review Reference - TEST
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-02
|
||||
task=node_unit_tests, plan=0, tag=TEST
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW.md` → `code_review_0.log`
|
||||
2. `PLAN.md` → `plan_0.log`
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [TEST-1] transport/frame 유닛 테스트 | [ ] |
|
||||
| [TEST-2] transport/codec 유닛 테스트 | [ ] |
|
||||
| [TEST-3] mock adapter 유닛 테스트 | [ ] |
|
||||
| [TEST-4] router 유닛 테스트 | [ ] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- frame_test.go: `readFrame` / `writeFrame` 모두 `package transport` 화이트박스로 접근하는지 확인
|
||||
- codec_test.go: 잘못된 JSON 입력 시 nil 이 아닌 error 반환 여부
|
||||
- mock_test.go: 이벤트 순서가 start → delta+ → complete 이고 RunID가 모든 이벤트에 일관되게 전파되는지 확인
|
||||
- mock_test.go: cancel 테스트가 `context.Canceled` / `context.DeadlineExceeded` 외 에러는 실패로 처리하는지 확인
|
||||
- router_test.go: 빈 registry 케이스와 미등록 어댑터 케이스 모두 error 반환하는지 확인
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### TEST-1 중간 검증
|
||||
```
|
||||
$ go test ./apps/node/internal/transport/... -run TestWriteReadFrame -v
|
||||
$ go test ./apps/node/internal/transport/... -run TestReadFrame -v
|
||||
(output)
|
||||
```
|
||||
|
||||
### TEST-2 중간 검증
|
||||
```
|
||||
$ go test ./apps/node/internal/transport/... -run TestEncodeDecodeEnvelope -v
|
||||
$ go test ./apps/node/internal/transport/... -run TestDecodeEnvelope -v
|
||||
$ go test ./apps/node/internal/transport/... -run TestEncodeDecodePayload -v
|
||||
(output)
|
||||
```
|
||||
|
||||
### TEST-3 중간 검증
|
||||
```
|
||||
$ go test ./apps/node/internal/adapters/mock/... -v
|
||||
(output)
|
||||
```
|
||||
|
||||
### TEST-4 중간 검증
|
||||
```
|
||||
$ go test ./apps/node/internal/router/... -v
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ go test ./apps/node/internal/transport/... ./apps/node/internal/adapters/mock/... ./apps/node/internal/router/... -v -count=1
|
||||
$ go build ./...
|
||||
(output)
|
||||
```
|
||||
|
|
@ -1,481 +0,0 @@
|
|||
<!-- task=node_unit_tests plan=0 tag=TEST -->
|
||||
|
||||
# Node 유닛 테스트 추가
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
각 체크리스트 항목을 완료한 뒤 `[x]`로 표시하세요.
|
||||
중간 검증 명령을 실제로 실행하고 출력을 `CODE_REVIEW.md` 검증 결과 섹션에 붙여 넣으세요.
|
||||
계획과 다르게 구현한 부분은 반드시 `계획 대비 변경 사항`에 기록하세요.
|
||||
|
||||
## 배경
|
||||
|
||||
IOP 노드 스캐폴드가 완성되었으나 테스트 파일이 없다.
|
||||
transport 레이어(frame/codec)와 핵심 컴포넌트(mock adapter, router)는
|
||||
외부 의존성 없이 순수하게 테스트 가능한 구조이므로 유닛 테스트를 추가해
|
||||
회귀 방지 기반을 만든다.
|
||||
|
||||
---
|
||||
|
||||
### [TEST-1] transport/frame 유닛 테스트
|
||||
|
||||
**문제**
|
||||
|
||||
[apps/node/internal/transport/frame.go:16-46](apps/node/internal/transport/frame.go)의
|
||||
`readFrame` / `writeFrame` 함수가 4-byte length-prefix 프레이밍을 올바르게 수행하는지
|
||||
검증하는 테스트가 없다. maxFrameSize 초과 케이스도 미검증.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
`package transport` (화이트박스) 테스트 파일을 생성한다.
|
||||
unexported 함수를 같은 패키지에서 직접 호출한다.
|
||||
|
||||
```go
|
||||
// apps/node/internal/transport/frame_test.go (신규)
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteReadFrame_roundtrip(t *testing.T) {
|
||||
payload := []byte(`{"type":"heartbeat","timestamp":1234}`)
|
||||
var buf bytes.Buffer
|
||||
if err := writeFrame(&buf, payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := readFrame(&buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, payload) {
|
||||
t.Errorf("round-trip mismatch: got %q, want %q", got, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadFrame_empty(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := writeFrame(&buf, []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := readFrame(&buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("expected empty payload, got %d bytes", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFrame_tooLarge(t *testing.T) {
|
||||
// 헤더에 maxFrameSize+1 을 직접 기록
|
||||
var buf bytes.Buffer
|
||||
hdr := make([]byte, 4)
|
||||
// encoding/binary 없이 직접 big-endian 기록
|
||||
sz := uint32(maxFrameSize + 1)
|
||||
hdr[0] = byte(sz >> 24)
|
||||
hdr[1] = byte(sz >> 16)
|
||||
hdr[2] = byte(sz >> 8)
|
||||
hdr[3] = byte(sz)
|
||||
buf.Write(hdr)
|
||||
_, err := readFrame(&buf)
|
||||
if err == nil {
|
||||
t.Error("expected error for oversized frame, got nil")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `apps/node/internal/transport/frame_test.go` 신규 생성
|
||||
- [ ] `TestWriteReadFrame_roundtrip` 작성
|
||||
- [ ] `TestWriteReadFrame_empty` 작성
|
||||
- [ ] `TestReadFrame_tooLarge` 작성
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
이 항목 자체가 테스트 작성 작업이므로 별도 추가 테스트 없음.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
```bash
|
||||
go test ./apps/node/internal/transport/... -run TestWriteReadFrame -v
|
||||
go test ./apps/node/internal/transport/... -run TestReadFrame -v
|
||||
```
|
||||
|
||||
기대 결과: 3개 테스트 PASS
|
||||
|
||||
---
|
||||
|
||||
### [TEST-2] transport/codec 유닛 테스트
|
||||
|
||||
**문제**
|
||||
|
||||
[apps/node/internal/transport/codec.go:10-29](apps/node/internal/transport/codec.go)의
|
||||
`encodeEnvelope` / `decodeEnvelope` / `encodePayload` / `decodePayload` 함수에 테스트 없음.
|
||||
JSON 인코딩 라운드트립과 잘못된 JSON 입력 시 에러 반환 여부가 미검증.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
```go
|
||||
// apps/node/internal/transport/codec_test.go (신규)
|
||||
package transport
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"iop/packages/protocol"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeEnvelope_roundtrip(t *testing.T) {
|
||||
orig := &protocol.Envelope{
|
||||
ProtocolVersion: protocol.ProtocolVersion,
|
||||
RequestID: "req-001",
|
||||
RunID: "run-abc",
|
||||
Type: protocol.TypeHeartbeat,
|
||||
Payload: []byte(`{"timestamp":999}`),
|
||||
}
|
||||
data, err := encodeEnvelope(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := decodeEnvelope(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.RequestID != orig.RequestID {
|
||||
t.Errorf("RequestID: got %q, want %q", got.RequestID, orig.RequestID)
|
||||
}
|
||||
if got.RunID != orig.RunID {
|
||||
t.Errorf("RunID: got %q, want %q", got.RunID, orig.RunID)
|
||||
}
|
||||
if got.Type != orig.Type {
|
||||
t.Errorf("Type: got %q, want %q", got.Type, orig.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEnvelope_invalidJSON(t *testing.T) {
|
||||
_, err := decodeEnvelope([]byte(`not-json`))
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid JSON, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodePayload_roundtrip(t *testing.T) {
|
||||
type inner struct {
|
||||
Foo string `json:"foo"`
|
||||
Bar int `json:"bar"`
|
||||
}
|
||||
orig := inner{Foo: "hello", Bar: 42}
|
||||
data, err := encodePayload(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got inner
|
||||
if err := decodePayload(data, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Foo != orig.Foo || got.Bar != orig.Bar {
|
||||
t.Errorf("payload mismatch: got %+v, want %+v", got, orig)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `apps/node/internal/transport/codec_test.go` 신규 생성
|
||||
- [ ] `TestEncodeDecodeEnvelope_roundtrip` 작성
|
||||
- [ ] `TestDecodeEnvelope_invalidJSON` 작성
|
||||
- [ ] `TestEncodeDecodePayload_roundtrip` 작성
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
이 항목 자체가 테스트 작성 작업.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
```bash
|
||||
go test ./apps/node/internal/transport/... -run TestEncodeDecodeEnvelope -v
|
||||
go test ./apps/node/internal/transport/... -run TestDecodeEnvelope -v
|
||||
go test ./apps/node/internal/transport/... -run TestEncodeDecodePayload -v
|
||||
```
|
||||
|
||||
기대 결과: 3개 테스트 PASS
|
||||
|
||||
---
|
||||
|
||||
### [TEST-3] mock adapter 유닛 테스트
|
||||
|
||||
**문제**
|
||||
|
||||
[apps/node/internal/adapters/mock/mock.go:38-72](apps/node/internal/adapters/mock/mock.go)의
|
||||
`Execute` 함수가 start → delta+ → complete 순서로 이벤트를 방출하는지,
|
||||
context cancel 시 조기 종료하는지 미검증.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
`collectSink` 헬퍼 타입으로 이벤트를 수집한 뒤 순서와 타입을 검증한다.
|
||||
|
||||
```go
|
||||
// apps/node/internal/adapters/mock/mock_test.go (신규)
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"iop/apps/node/internal/runtime"
|
||||
)
|
||||
|
||||
type collectSink struct {
|
||||
events []runtime.RuntimeEvent
|
||||
}
|
||||
|
||||
func (c *collectSink) Emit(_ context.Context, e runtime.RuntimeEvent) error {
|
||||
c.events = append(c.events, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMockExecute_sequence(t *testing.T) {
|
||||
m := New(zap.NewNop())
|
||||
sink := &collectSink{}
|
||||
spec := runtime.ExecutionSpec{
|
||||
RunID: "test-run-001",
|
||||
Adapter: Name,
|
||||
Input: map[string]any{"prompt": "hello world"},
|
||||
}
|
||||
|
||||
if err := m.Execute(context.Background(), spec, sink); err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
if len(sink.events) < 2 {
|
||||
t.Fatalf("expected at least 2 events, got %d", len(sink.events))
|
||||
}
|
||||
|
||||
// 첫 이벤트는 반드시 start
|
||||
if sink.events[0].Type != runtime.EventTypeStart {
|
||||
t.Errorf("first event: got %q, want %q", sink.events[0].Type, runtime.EventTypeStart)
|
||||
}
|
||||
// 마지막 이벤트는 반드시 complete
|
||||
last := sink.events[len(sink.events)-1]
|
||||
if last.Type != runtime.EventTypeComplete {
|
||||
t.Errorf("last event: got %q, want %q", last.Type, runtime.EventTypeComplete)
|
||||
}
|
||||
// complete 이벤트에 Usage 포함 여부
|
||||
if last.Usage == nil {
|
||||
t.Error("complete event: expected Usage, got nil")
|
||||
}
|
||||
// RunID 일관성
|
||||
for i, e := range sink.events {
|
||||
if e.RunID != spec.RunID {
|
||||
t.Errorf("event[%d].RunID: got %q, want %q", i, e.RunID, spec.RunID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockExecute_cancel(t *testing.T) {
|
||||
m := New(zap.NewNop())
|
||||
sink := &collectSink{}
|
||||
spec := runtime.ExecutionSpec{
|
||||
RunID: "test-cancel-001",
|
||||
Adapter: Name,
|
||||
Input: map[string]any{"prompt": "long long long long long long long long prompt"},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := m.Execute(ctx, spec, sink)
|
||||
// context cancel or deadline exceeded 는 정상 종료 케이스
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockCapabilities(t *testing.T) {
|
||||
m := New(zap.NewNop())
|
||||
caps, err := m.Capabilities(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if caps.AdapterName != Name {
|
||||
t.Errorf("AdapterName: got %q, want %q", caps.AdapterName, Name)
|
||||
}
|
||||
if len(caps.Models) == 0 {
|
||||
t.Error("expected at least one model")
|
||||
}
|
||||
if caps.MaxConcurrency <= 0 {
|
||||
t.Error("expected positive MaxConcurrency")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `apps/node/internal/adapters/mock/mock_test.go` 신규 생성
|
||||
- [ ] `collectSink` 헬퍼 타입 작성
|
||||
- [ ] `TestMockExecute_sequence` 작성
|
||||
- [ ] `TestMockExecute_cancel` 작성
|
||||
- [ ] `TestMockCapabilities` 작성
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
이 항목 자체가 테스트 작성 작업.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
```bash
|
||||
go test ./apps/node/internal/adapters/mock/... -v
|
||||
```
|
||||
|
||||
기대 결과: 3개 테스트 PASS (cancel 테스트는 50ms 내 종료)
|
||||
|
||||
---
|
||||
|
||||
### [TEST-4] router 유닛 테스트
|
||||
|
||||
**문제**
|
||||
|
||||
[apps/node/internal/router/router.go:24-47](apps/node/internal/router/router.go)의
|
||||
`Resolve` 함수가 adapter 이름 미지정 시 default 선택, 명시 시 해당 어댑터 선택,
|
||||
미등록 어댑터 요청 시 에러 반환하는지 미검증.
|
||||
|
||||
**해결 방법**
|
||||
|
||||
```go
|
||||
// apps/node/internal/router/router_test.go (신규)
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"iop/apps/node/internal/adapters"
|
||||
"iop/apps/node/internal/adapters/mock"
|
||||
"iop/apps/node/internal/runtime"
|
||||
)
|
||||
|
||||
func newTestRegistry() *adapters.Registry {
|
||||
reg := adapters.NewRegistry()
|
||||
reg.Register(mock.New(zap.NewNop()))
|
||||
return reg
|
||||
}
|
||||
|
||||
func TestRouter_resolveDefault(t *testing.T) {
|
||||
r := New(newTestRegistry(), zap.NewNop())
|
||||
spec, err := r.Resolve(context.Background(), runtime.RunRequest{
|
||||
RunID: "r-001",
|
||||
// Adapter 비워두면 default 선택
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if spec.Adapter != mock.Name {
|
||||
t.Errorf("Adapter: got %q, want %q", spec.Adapter, mock.Name)
|
||||
}
|
||||
if spec.RunID != "r-001" {
|
||||
t.Errorf("RunID: got %q, want %q", spec.RunID, "r-001")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_resolveExplicit(t *testing.T) {
|
||||
r := New(newTestRegistry(), zap.NewNop())
|
||||
spec, err := r.Resolve(context.Background(), runtime.RunRequest{
|
||||
RunID: "r-002",
|
||||
Adapter: mock.Name,
|
||||
Model: "mock-echo",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if spec.Adapter != mock.Name {
|
||||
t.Errorf("Adapter: got %q, want %q", spec.Adapter, mock.Name)
|
||||
}
|
||||
if spec.Model != "mock-echo" {
|
||||
t.Errorf("Model: got %q, want %q", spec.Model, "mock-echo")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_resolveUnknown(t *testing.T) {
|
||||
r := New(newTestRegistry(), zap.NewNop())
|
||||
_, err := r.Resolve(context.Background(), runtime.RunRequest{
|
||||
RunID: "r-003",
|
||||
Adapter: "nonexistent-adapter",
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("expected error for unknown adapter, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_emptyRegistry(t *testing.T) {
|
||||
r := New(adapters.NewRegistry(), zap.NewNop())
|
||||
_, err := r.Resolve(context.Background(), runtime.RunRequest{RunID: "r-004"})
|
||||
if err == nil {
|
||||
t.Error("expected error for empty registry, got nil")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일 및 체크리스트**
|
||||
|
||||
- [ ] `apps/node/internal/router/router_test.go` 신규 생성
|
||||
- [ ] `newTestRegistry` 헬퍼 작성
|
||||
- [ ] `TestRouter_resolveDefault` 작성
|
||||
- [ ] `TestRouter_resolveExplicit` 작성
|
||||
- [ ] `TestRouter_resolveUnknown` 작성
|
||||
- [ ] `TestRouter_emptyRegistry` 작성
|
||||
|
||||
**테스트 작성**
|
||||
|
||||
이 항목 자체가 테스트 작성 작업.
|
||||
|
||||
**중간 검증**
|
||||
|
||||
```bash
|
||||
go test ./apps/node/internal/router/... -v
|
||||
```
|
||||
|
||||
기대 결과: 4개 테스트 PASS
|
||||
|
||||
---
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
TEST-1, TEST-2 는 외부 iop 패키지 의존성이 없어 독립 실행 가능.
|
||||
TEST-3 은 `go.uber.org/zap` 의존 → `go mod tidy` 완료 후 실행.
|
||||
TEST-4 는 mock 패키지 의존 → TEST-3 파일 존재 후 실행.
|
||||
|
||||
권장 순서: TEST-1 → TEST-2 → TEST-3 → TEST-4
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/node/internal/transport/frame_test.go` | TEST-1 |
|
||||
| `apps/node/internal/transport/codec_test.go` | TEST-2 |
|
||||
| `apps/node/internal/adapters/mock/mock_test.go` | TEST-3 |
|
||||
| `apps/node/internal/router/router_test.go` | TEST-4 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
# 의존성 먼저 해결
|
||||
go mod tidy
|
||||
|
||||
# 전체 테스트
|
||||
go test ./apps/node/internal/transport/... \
|
||||
./apps/node/internal/adapters/mock/... \
|
||||
./apps/node/internal/router/... \
|
||||
-v -count=1
|
||||
|
||||
# 전체 빌드 깨짐 없음 확인
|
||||
go build ./...
|
||||
```
|
||||
|
||||
기대 결과: 전체 테스트 PASS, 빌드 에러 없음
|
||||
|
|
@ -5,8 +5,14 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"iop/apps/edge/internal/bootstrap"
|
||||
"iop/packages/config"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
func main() {
|
||||
if err := rootCmd().Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
|
@ -17,18 +23,24 @@ func main() {
|
|||
func rootCmd() *cobra.Command {
|
||||
root := &cobra.Command{
|
||||
Use: "iop-edge",
|
||||
Short: "IOP Edge — OpenAI-compatible API gateway (placeholder)",
|
||||
Long: `iop-edge exposes an OpenAI-compatible HTTP API and routes requests
|
||||
to IOP nodes via the TCP + Protobuf internal transport.
|
||||
|
||||
This component is a placeholder. Implementation is planned after the node is stable.`,
|
||||
Short: "IOP Edge — node gateway server",
|
||||
}
|
||||
root.AddCommand(&cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the edge server",
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Println("iop-edge serve: not yet implemented")
|
||||
},
|
||||
})
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/edge.yaml", "config file path")
|
||||
root.AddCommand(serveCmd())
|
||||
return root
|
||||
}
|
||||
|
||||
func serveCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the edge server",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
cfg, err := config.LoadEdge(cfgFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
fx.New(bootstrap.Module(cfg)).Run()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
apps/edge/internal/bootstrap/module.go
Normal file
52
apps/edge/internal/bootstrap/module.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
|
||||
edgenode "iop/apps/edge/internal/node"
|
||||
"iop/apps/edge/internal/transport"
|
||||
"iop/packages/config"
|
||||
"iop/packages/observability"
|
||||
)
|
||||
|
||||
func Module(cfg *config.EdgeConfig) fx.Option {
|
||||
return fx.Options(
|
||||
fx.Provide(
|
||||
func() *config.EdgeConfig { return cfg },
|
||||
|
||||
func(cfg *config.EdgeConfig) (*zap.Logger, error) {
|
||||
return observability.NewLogger(cfg.Logging.Level)
|
||||
},
|
||||
|
||||
func() *edgenode.Registry {
|
||||
return edgenode.NewRegistry()
|
||||
},
|
||||
|
||||
func(cfg *config.EdgeConfig, reg *edgenode.Registry, logger *zap.Logger) (*transport.Server, error) {
|
||||
return transport.NewServer(cfg.Server.Listen, reg, logger)
|
||||
},
|
||||
),
|
||||
|
||||
fx.Invoke(func(lc fx.Lifecycle, srv *transport.Server, cfg *config.EdgeConfig, logger *zap.Logger) {
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
if err := srv.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if err := observability.ServeMetrics(cfg.Metrics.Port); err != nil {
|
||||
logger.Warn("metrics server exited", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
},
|
||||
OnStop: func(_ context.Context) error {
|
||||
return srv.Stop()
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
71
apps/edge/internal/node/registry.go
Normal file
71
apps/edge/internal/node/registry.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
// NodeEntry represents one connected node.
|
||||
type NodeEntry struct {
|
||||
NodeID string
|
||||
Client *toki.TcpClient
|
||||
Adapters []*iop.AdapterInfo
|
||||
}
|
||||
|
||||
// Registry manages all nodes connected to edge.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
nodes map[string]*NodeEntry
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{nodes: make(map[string]*NodeEntry)}
|
||||
}
|
||||
|
||||
func (r *Registry) Register(entry *NodeEntry) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.nodes[entry.NodeID] = entry
|
||||
}
|
||||
|
||||
func (r *Registry) Unregister(nodeID string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
delete(r.nodes, nodeID)
|
||||
}
|
||||
|
||||
func (r *Registry) Get(nodeID string) (*NodeEntry, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
e, ok := r.nodes[nodeID]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
func (r *Registry) All() []*NodeEntry {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
out := make([]*NodeEntry, 0, len(r.nodes))
|
||||
for _, e := range r.nodes {
|
||||
out = append(out, e)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Registry) Count() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.nodes)
|
||||
}
|
||||
|
||||
func (r *Registry) Pick() (*NodeEntry, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
for _, e := range r.nodes {
|
||||
return e, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no nodes connected")
|
||||
}
|
||||
39
apps/edge/internal/node/registry_test.go
Normal file
39
apps/edge/internal/node/registry_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
|
||||
edgenode "iop/apps/edge/internal/node"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
func TestRegistry_RegisterAndCount(t *testing.T) {
|
||||
reg := edgenode.NewRegistry()
|
||||
entry := &edgenode.NodeEntry{
|
||||
NodeID: "node-001",
|
||||
Client: &toki.TcpClient{},
|
||||
Adapters: []*iop.AdapterInfo{{Name: "mock"}},
|
||||
}
|
||||
reg.Register(entry)
|
||||
if reg.Count() != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", reg.Count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_Pick_Empty(t *testing.T) {
|
||||
reg := edgenode.NewRegistry()
|
||||
if _, err := reg.Pick(); err == nil {
|
||||
t.Fatal("expected error on empty registry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_Unregister(t *testing.T) {
|
||||
reg := edgenode.NewRegistry()
|
||||
reg.Register(&edgenode.NodeEntry{NodeID: "node-001"})
|
||||
reg.Unregister("node-001")
|
||||
if reg.Count() != 0 {
|
||||
t.Fatalf("expected 0 nodes after unregister, got %d", reg.Count())
|
||||
}
|
||||
}
|
||||
112
apps/edge/internal/transport/server.go
Normal file
112
apps/edge/internal/transport/server.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
edgenode "iop/apps/edge/internal/node"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
const (
|
||||
heartbeatIntervalSec = 30
|
||||
heartbeatWaitSec = 10
|
||||
capabilityTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
func edgeParserMap() toki.ParserMap {
|
||||
return toki.ParserMap{
|
||||
toki.TypeNameOf(&iop.RunEvent{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.RunEvent{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
toki.TypeNameOf(&iop.CapabilityResponse{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.CapabilityResponse{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Server wraps proto-socket TcpServer and manages node connections.
|
||||
type Server struct {
|
||||
tcp *toki.TcpServer
|
||||
listen string
|
||||
registry *edgenode.Registry
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewServer(listen string, registry *edgenode.Registry, logger *zap.Logger) (*Server, error) {
|
||||
host, portStr, err := net.SplitHostPort(listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Server{listen: listen, registry: registry, logger: logger}
|
||||
s.tcp = toki.NewTcpServer(host, port, func(conn net.Conn) *toki.TcpClient {
|
||||
return toki.NewTcpClient(conn, heartbeatIntervalSec, heartbeatWaitSec, edgeParserMap())
|
||||
})
|
||||
s.tcp.OnClientConnected = s.onNodeConnected
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
if err := s.tcp.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
s.logger.Info("edge listening for nodes", zap.String("addr", s.listen))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
return s.tcp.Stop()
|
||||
}
|
||||
|
||||
func (s *Server) onNodeConnected(client *toki.TcpClient) {
|
||||
s.logger.Info("node connection established")
|
||||
|
||||
toki.AddListenerTyped[*iop.RunEvent](&client.Communicator, func(e *iop.RunEvent) {
|
||||
s.logger.Debug("run event received",
|
||||
zap.String("run_id", e.GetRunId()),
|
||||
zap.String("type", e.GetType()),
|
||||
)
|
||||
})
|
||||
|
||||
go func() {
|
||||
resp, err := toki.SendRequestTyped[*iop.CapabilityRequest, *iop.CapabilityResponse](
|
||||
&client.Communicator,
|
||||
&iop.CapabilityRequest{},
|
||||
capabilityTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
s.logger.Warn("capability request failed", zap.Error(err))
|
||||
_ = client.Close()
|
||||
return
|
||||
}
|
||||
|
||||
entry := &edgenode.NodeEntry{
|
||||
NodeID: resp.GetNodeId(),
|
||||
Client: client,
|
||||
Adapters: resp.GetAdapters(),
|
||||
}
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) {
|
||||
s.registry.Unregister(entry.NodeID)
|
||||
s.logger.Info("node unregistered", zap.String("node_id", entry.NodeID))
|
||||
})
|
||||
|
||||
s.registry.Register(entry)
|
||||
s.logger.Info("node registered",
|
||||
zap.String("node_id", entry.NodeID),
|
||||
zap.Int("adapters", len(entry.Adapters)),
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"iop/apps/node/internal/router"
|
||||
"iop/apps/node/internal/runtime"
|
||||
"iop/apps/node/internal/store"
|
||||
"iop/apps/node/internal/transport"
|
||||
"iop/packages/config"
|
||||
"iop/packages/observability"
|
||||
)
|
||||
|
|
@ -67,9 +68,15 @@ func Module(cfg *config.NodeConfig) fx.Option {
|
|||
},
|
||||
),
|
||||
|
||||
fx.Invoke(func(lc fx.Lifecycle, cfg *config.NodeConfig, logger *zap.Logger) {
|
||||
fx.Invoke(func(lc fx.Lifecycle, n *node.Node, cfg *config.NodeConfig, logger *zap.Logger) {
|
||||
var sess *transport.Session
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
s, err := transport.DialEdge(ctx, cfg.Transport.EdgeAddr, n, cfg.Node.ID, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess = s
|
||||
go func() {
|
||||
if err := observability.ServeMetrics(cfg.Metrics.Port); err != nil {
|
||||
logger.Warn("metrics server exited", zap.Error(err))
|
||||
|
|
@ -77,7 +84,12 @@ func Module(cfg *config.NodeConfig) fx.Option {
|
|||
}()
|
||||
return nil
|
||||
},
|
||||
OnStop: func(_ context.Context) error { return nil },
|
||||
OnStop: func(_ context.Context) error {
|
||||
if sess != nil {
|
||||
return sess.Close()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ package node
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"iop/apps/node/internal/adapters"
|
||||
"iop/apps/node/internal/runtime"
|
||||
"iop/apps/node/internal/store"
|
||||
"iop/apps/node/internal/transport"
|
||||
"iop/packages/config"
|
||||
"iop/packages/protocol"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
// Node implements transport.Handler and coordinates the full execution pipeline.
|
||||
|
|
@ -45,22 +45,22 @@ func New(
|
|||
}
|
||||
|
||||
// OnRunRequest handles an incoming RunRequest from a transport Session.
|
||||
func (n *Node) OnRunRequest(ctx context.Context, sess *transport.Session, req *protocol.RunRequest) error {
|
||||
func (n *Node) OnRunRequest(ctx context.Context, sess *transport.Session, req *iop.RunRequest) error {
|
||||
n.logger.Info("run request received",
|
||||
zap.String("run_id", req.RunID),
|
||||
zap.String("adapter", req.Adapter),
|
||||
zap.String("model", req.Model),
|
||||
zap.String("run_id", req.GetRunId()),
|
||||
zap.String("adapter", req.GetAdapter()),
|
||||
zap.String("model", req.GetModel()),
|
||||
)
|
||||
|
||||
rr := runtime.RunRequest{
|
||||
RunID: req.RunID,
|
||||
Adapter: req.Adapter,
|
||||
Model: req.Model,
|
||||
Workspace: req.Workspace,
|
||||
Policy: req.Policy,
|
||||
Input: req.Input,
|
||||
TimeoutSec: req.TimeoutSec,
|
||||
Metadata: req.Metadata,
|
||||
RunID: req.GetRunId(),
|
||||
Adapter: req.GetAdapter(),
|
||||
Model: req.GetModel(),
|
||||
Workspace: req.GetWorkspace(),
|
||||
Policy: structAsMap(req.GetPolicy()),
|
||||
Input: structAsMap(req.GetInput()),
|
||||
TimeoutSec: int(req.GetTimeoutSec()),
|
||||
Metadata: req.GetMetadata(),
|
||||
}
|
||||
|
||||
spec, err := n.router.Resolve(ctx, rr)
|
||||
|
|
@ -90,7 +90,7 @@ func (n *Node) OnRunRequest(ctx context.Context, sess *transport.Session, req *p
|
|||
defer sess.DeregisterCancel(spec.RunID)
|
||||
}
|
||||
|
||||
sink := &sessionSink{sess: sess, runID: spec.RunID}
|
||||
sink := &sessionSink{sess: sess}
|
||||
execErr := adapter.Execute(execCtx, spec, sink)
|
||||
|
||||
status := "completed"
|
||||
|
|
@ -105,65 +105,57 @@ func (n *Node) OnRunRequest(ctx context.Context, sess *transport.Session, req *p
|
|||
}
|
||||
|
||||
// OnCapabilityRequest returns the node's available adapters.
|
||||
func (n *Node) OnCapabilityRequest(_ context.Context, _ *transport.Session) (*protocol.CapabilityResponse, error) {
|
||||
resp := &protocol.CapabilityResponse{NodeID: n.nodeID}
|
||||
func (n *Node) OnCapabilityRequest(_ context.Context, _ *transport.Session) (*iop.CapabilityResponse, error) {
|
||||
resp := &iop.CapabilityResponse{NodeId: n.nodeID}
|
||||
for _, a := range n.registry.All() {
|
||||
caps, err := a.Capabilities(context.Background())
|
||||
if err != nil {
|
||||
n.logger.Warn("capabilities query failed", zap.String("adapter", a.Name()), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
resp.Adapters = append(resp.Adapters, protocol.AdapterInfo{
|
||||
resp.Adapters = append(resp.Adapters, &iop.AdapterInfo{
|
||||
Name: caps.AdapterName,
|
||||
Models: caps.Models,
|
||||
MaxConcurrency: caps.MaxConcurrency,
|
||||
MaxConcurrency: int32(caps.MaxConcurrency),
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// OnCancel cancels a running execution.
|
||||
func (n *Node) OnCancel(_ context.Context, sess *transport.Session, req *protocol.CancelRequest) error {
|
||||
n.logger.Info("cancel request", zap.String("run_id", req.RunID))
|
||||
sess.CancelRun(req.RunID)
|
||||
func (n *Node) OnCancel(_ context.Context, sess *transport.Session, req *iop.CancelRequest) error {
|
||||
n.logger.Info("cancel request", zap.String("run_id", req.GetRunId()))
|
||||
sess.CancelRun(req.GetRunId())
|
||||
return nil
|
||||
}
|
||||
|
||||
// sessionSink wraps a transport.Session to implement runtime.EventSink.
|
||||
type sessionSink struct {
|
||||
sess *transport.Session
|
||||
runID string
|
||||
sess *transport.Session
|
||||
}
|
||||
|
||||
func (s *sessionSink) Emit(_ context.Context, event runtime.RuntimeEvent) error {
|
||||
var usageInfo *protocol.UsageInfo
|
||||
if event.Usage != nil {
|
||||
usageInfo = &protocol.UsageInfo{
|
||||
InputTokens: event.Usage.InputTokens,
|
||||
OutputTokens: event.Usage.OutputTokens,
|
||||
}
|
||||
}
|
||||
|
||||
re := protocol.RunEvent{
|
||||
RunID: event.RunID,
|
||||
re := &iop.RunEvent{
|
||||
RunId: event.RunID,
|
||||
Type: string(event.Type),
|
||||
Delta: event.Delta,
|
||||
Message: event.Message,
|
||||
Error: event.Error,
|
||||
Usage: usageInfo,
|
||||
Metadata: event.Metadata,
|
||||
Timestamp: event.Timestamp.UnixNano(),
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(re)
|
||||
if err != nil {
|
||||
return err
|
||||
if event.Usage != nil {
|
||||
re.Usage = &iop.Usage{
|
||||
InputTokens: int32(event.Usage.InputTokens),
|
||||
OutputTokens: int32(event.Usage.OutputTokens),
|
||||
}
|
||||
}
|
||||
|
||||
return s.sess.Send(&protocol.Envelope{
|
||||
ProtocolVersion: protocol.ProtocolVersion,
|
||||
RunID: s.runID,
|
||||
Type: protocol.TypeRunEvent,
|
||||
Payload: payload,
|
||||
})
|
||||
return s.sess.Send(re)
|
||||
}
|
||||
|
||||
func structAsMap(s *structpb.Struct) map[string]any {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return s.AsMap()
|
||||
}
|
||||
|
|
|
|||
159
apps/node/internal/node/node_test.go
Normal file
159
apps/node/internal/node/node_test.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"iop/apps/node/internal/adapters"
|
||||
"iop/apps/node/internal/node"
|
||||
"iop/apps/node/internal/runtime"
|
||||
"iop/apps/node/internal/store"
|
||||
"iop/apps/node/internal/transport"
|
||||
"iop/packages/config"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
type noEmitAdapter struct{}
|
||||
|
||||
func (a *noEmitAdapter) Name() string { return "test" }
|
||||
|
||||
func (a *noEmitAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
|
||||
return runtime.Capabilities{
|
||||
AdapterName: "test",
|
||||
Models: []string{"v1"},
|
||||
MaxConcurrency: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *noEmitAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fixedRouter struct {
|
||||
adapterName string
|
||||
}
|
||||
|
||||
func (r *fixedRouter) Resolve(_ context.Context, req runtime.RunRequest) (runtime.ExecutionSpec, error) {
|
||||
return runtime.ExecutionSpec{
|
||||
RunID: req.RunID,
|
||||
Adapter: r.adapterName,
|
||||
Model: req.Model,
|
||||
Workspace: req.Workspace,
|
||||
Policy: req.Policy,
|
||||
Input: req.Input,
|
||||
TimeoutSec: req.TimeoutSec,
|
||||
Metadata: req.Metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type errorRouter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *errorRouter) Resolve(_ context.Context, _ runtime.RunRequest) (runtime.ExecutionSpec, error) {
|
||||
return runtime.ExecutionSpec{}, r.err
|
||||
}
|
||||
|
||||
func makeNode(t *testing.T, rtr runtime.Router, reg *adapters.Registry) *node.Node {
|
||||
t.Helper()
|
||||
|
||||
st, err := store.New(":memory:", zap.NewNop())
|
||||
if err != nil {
|
||||
t.Fatalf("store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := st.Close(); err != nil {
|
||||
t.Fatalf("close store: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
cfg := &config.NodeConfig{Node: config.NodeInfo{ID: "test-node"}}
|
||||
return node.New(cfg, rtr, reg, st, zap.NewNop())
|
||||
}
|
||||
|
||||
func TestOnCapabilityRequest(t *testing.T) {
|
||||
reg := adapters.NewRegistry()
|
||||
reg.Register(&noEmitAdapter{})
|
||||
n := makeNode(t, &fixedRouter{adapterName: "test"}, reg)
|
||||
|
||||
resp, err := n.OnCapabilityRequest(context.Background(), &transport.Session{})
|
||||
if err != nil {
|
||||
t.Fatalf("capability request: %v", err)
|
||||
}
|
||||
if resp.GetNodeId() != "test-node" {
|
||||
t.Fatalf("unexpected node ID: %q", resp.GetNodeId())
|
||||
}
|
||||
if len(resp.GetAdapters()) != 1 {
|
||||
t.Fatalf("expected 1 adapter, got %d", len(resp.GetAdapters()))
|
||||
}
|
||||
adapter := resp.GetAdapters()[0]
|
||||
if adapter.GetName() != "test" {
|
||||
t.Fatalf("unexpected adapter name: %q", adapter.GetName())
|
||||
}
|
||||
if len(adapter.GetModels()) != 1 || adapter.GetModels()[0] != "v1" {
|
||||
t.Fatalf("unexpected models: %v", adapter.GetModels())
|
||||
}
|
||||
if adapter.GetMaxConcurrency() != 1 {
|
||||
t.Fatalf("unexpected max concurrency: %d", adapter.GetMaxConcurrency())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnRunRequest_RouterError(t *testing.T) {
|
||||
reg := adapters.NewRegistry()
|
||||
n := makeNode(t, &errorRouter{err: errors.New("boom")}, reg)
|
||||
|
||||
err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{RunId: "run-1"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "node: resolve:") {
|
||||
t.Fatalf("expected resolve prefix, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnRunRequest_AdapterNotFound(t *testing.T) {
|
||||
reg := adapters.NewRegistry()
|
||||
n := makeNode(t, &fixedRouter{adapterName: "missing"}, reg)
|
||||
|
||||
err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{RunId: "run-1"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not found after routing") {
|
||||
t.Fatalf("expected adapter lookup error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnRunRequest_Success(t *testing.T) {
|
||||
reg := adapters.NewRegistry()
|
||||
reg.Register(&noEmitAdapter{})
|
||||
n := makeNode(t, &fixedRouter{adapterName: "test"}, reg)
|
||||
|
||||
err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{
|
||||
RunId: "run-1",
|
||||
Adapter: "test",
|
||||
Model: "v1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("run request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCancel_CallsCancelFn(t *testing.T) {
|
||||
reg := adapters.NewRegistry()
|
||||
n := makeNode(t, &fixedRouter{adapterName: "test"}, reg)
|
||||
sess := &transport.Session{}
|
||||
called := false
|
||||
sess.RegisterCancel("run-1", func() { called = true })
|
||||
|
||||
if err := n.OnCancel(context.Background(), sess, &iop.CancelRequest{RunId: "run-1"}); err != nil {
|
||||
t.Fatalf("cancel: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("cancel function was not called")
|
||||
}
|
||||
}
|
||||
37
apps/node/internal/transport/client.go
Normal file
37
apps/node/internal/transport/client.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
heartbeatIntervalSec = 30
|
||||
heartbeatWaitSec = 10
|
||||
)
|
||||
|
||||
// DialEdge connects to the edge TCP server and returns a Session.
|
||||
func DialEdge(ctx context.Context, addr string, handler Handler, nodeID string, logger *zap.Logger) (*Session, error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport: invalid addr %q: %w", addr, err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport: invalid port %q: %w", portStr, err)
|
||||
}
|
||||
|
||||
client, err := toki.DialTcp(ctx, host, port, heartbeatIntervalSec, heartbeatWaitSec, nodeParserMap())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport: dial edge %s: %w", addr, err)
|
||||
}
|
||||
|
||||
sess := newSession(client, handler, nodeID, logger)
|
||||
logger.Info("connected to edge", zap.String("addr", addr), zap.String("node_id", nodeID))
|
||||
return sess, nil
|
||||
}
|
||||
25
apps/node/internal/transport/parser.go
Normal file
25
apps/node/internal/transport/parser.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
func nodeParserMap() toki.ParserMap {
|
||||
return toki.ParserMap{
|
||||
toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.RunRequest{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
toki.TypeNameOf(&iop.CancelRequest{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.CancelRequest{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
toki.TypeNameOf(&iop.CapabilityRequest{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.CapabilityRequest{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
}
|
||||
}
|
||||
65
apps/node/internal/transport/parser_test.go
Normal file
65
apps/node/internal/transport/parser_test.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
func TestNodeParserMap_RunRequest(t *testing.T) {
|
||||
parsers := nodeParserMap()
|
||||
original := &iop.RunRequest{RunId: "run-1", Adapter: "mock", Model: "v1"}
|
||||
payload, err := proto.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := parsers[toki.TypeNameOf(original)](payload)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
got := parsed.(*iop.RunRequest)
|
||||
if got.GetRunId() != original.GetRunId() ||
|
||||
got.GetAdapter() != original.GetAdapter() ||
|
||||
got.GetModel() != original.GetModel() {
|
||||
t.Fatalf("unexpected run request: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeParserMap_CancelRequest(t *testing.T) {
|
||||
parsers := nodeParserMap()
|
||||
original := &iop.CancelRequest{RunId: "run-1"}
|
||||
payload, err := proto.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := parsers[toki.TypeNameOf(original)](payload)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
got := parsed.(*iop.CancelRequest)
|
||||
if got.GetRunId() != original.GetRunId() {
|
||||
t.Fatalf("unexpected cancel request: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeParserMap_CapabilityRequest(t *testing.T) {
|
||||
parsers := nodeParserMap()
|
||||
original := &iop.CapabilityRequest{}
|
||||
payload, err := proto.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := parsers[toki.TypeNameOf(original)](payload)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if _, ok := parsed.(*iop.CapabilityRequest); !ok {
|
||||
t.Fatalf("unexpected capability request type: %T", parsed)
|
||||
}
|
||||
}
|
||||
92
apps/node/internal/transport/session.go
Normal file
92
apps/node/internal/transport/session.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
toki "git.toki-labs.com/toki/common-proto-socket/go"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
// Handler processes IOP messages received from edge.
|
||||
type Handler interface {
|
||||
OnRunRequest(ctx context.Context, sess *Session, req *iop.RunRequest) error
|
||||
OnCapabilityRequest(ctx context.Context, sess *Session) (*iop.CapabilityResponse, error)
|
||||
OnCancel(ctx context.Context, sess *Session, req *iop.CancelRequest) error
|
||||
}
|
||||
|
||||
// Session represents the node's persistent connection to edge.
|
||||
type Session struct {
|
||||
client *toki.TcpClient
|
||||
nodeID string
|
||||
logger *zap.Logger
|
||||
cancelFns sync.Map
|
||||
}
|
||||
|
||||
func newSession(client *toki.TcpClient, handler Handler, nodeID string, logger *zap.Logger) *Session {
|
||||
s := &Session{client: client, nodeID: nodeID, logger: logger}
|
||||
|
||||
toki.AddListenerTyped[*iop.RunRequest](&client.Communicator, func(req *iop.RunRequest) {
|
||||
go func() {
|
||||
if err := handler.OnRunRequest(context.Background(), s, req); err != nil {
|
||||
logger.Warn("run request error",
|
||||
zap.String("run_id", req.GetRunId()),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
toki.AddRequestListenerTyped[*iop.CapabilityRequest, *iop.CapabilityResponse](
|
||||
&client.Communicator,
|
||||
func(_ *iop.CapabilityRequest) (*iop.CapabilityResponse, error) {
|
||||
return handler.OnCapabilityRequest(context.Background(), s)
|
||||
},
|
||||
)
|
||||
|
||||
toki.AddListenerTyped[*iop.CancelRequest](&client.Communicator, func(req *iop.CancelRequest) {
|
||||
if err := handler.OnCancel(context.Background(), s, req); err != nil {
|
||||
logger.Warn("cancel error",
|
||||
zap.String("run_id", req.GetRunId()),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
client.AddDisconnectListener(func(_ *toki.TcpClient) {
|
||||
logger.Info("disconnected from edge", zap.String("node_id", nodeID))
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Send transmits a proto message to edge.
|
||||
func (s *Session) Send(m proto.Message) error {
|
||||
return s.client.Send(m)
|
||||
}
|
||||
|
||||
// IsAlive reports whether the connection is active.
|
||||
func (s *Session) IsAlive() bool { return s.client.IsAlive() }
|
||||
|
||||
// Close terminates the connection to edge.
|
||||
func (s *Session) Close() error { return s.client.Close() }
|
||||
|
||||
// RegisterCancel stores a cancel function for a running execution.
|
||||
func (s *Session) RegisterCancel(runID string, cancel context.CancelFunc) {
|
||||
s.cancelFns.Store(runID, cancel)
|
||||
}
|
||||
|
||||
// DeregisterCancel removes a run's cancel function after completion.
|
||||
func (s *Session) DeregisterCancel(runID string) {
|
||||
s.cancelFns.Delete(runID)
|
||||
}
|
||||
|
||||
// CancelRun invokes the cancel function for a run if one is registered.
|
||||
func (s *Session) CancelRun(runID string) {
|
||||
if v, ok := s.cancelFns.Load(runID); ok {
|
||||
v.(context.CancelFunc)()
|
||||
}
|
||||
}
|
||||
38
apps/node/internal/transport/session_test.go
Normal file
38
apps/node/internal/transport/session_test.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package transport_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"iop/apps/node/internal/transport"
|
||||
)
|
||||
|
||||
func TestSession_RegisterCancel_CancelRun(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
called := false
|
||||
|
||||
sess.RegisterCancel("run-1", func() { called = true })
|
||||
sess.CancelRun("run-1")
|
||||
|
||||
if !called {
|
||||
t.Fatal("cancel function was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_DeregisterCancel(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
called := false
|
||||
|
||||
sess.RegisterCancel("run-1", func() { called = true })
|
||||
sess.DeregisterCancel("run-1")
|
||||
sess.CancelRun("run-1")
|
||||
|
||||
if called {
|
||||
t.Fatal("cancel function should not have been called after deregister")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_CancelRun_UnknownID(t *testing.T) {
|
||||
sess := &transport.Session{}
|
||||
|
||||
sess.CancelRun("missing-run")
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
server:
|
||||
listen: "0.0.0.0:8080"
|
||||
listen: "0.0.0.0:9090"
|
||||
|
||||
tls:
|
||||
enabled: false
|
||||
|
|
|
|||
45
go.mod
45
go.mod
|
|
@ -3,6 +3,7 @@ module iop
|
|||
go 1.24
|
||||
|
||||
require (
|
||||
git.toki-labs.com/toki/common-proto-socket/go v0.0.0-00010101000000-000000000000
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
|
|
@ -12,3 +13,47 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.33.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/dig v1.18.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
nhooyr.io/websocket v1.8.17 // indirect
|
||||
)
|
||||
|
||||
replace git.toki-labs.com/toki/common-proto-socket/go => ../proto-socket/go
|
||||
|
|
|
|||
151
go.sum
Normal file
151
go.sum
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
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=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
|
||||
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw=
|
||||
go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
|
|
@ -5,14 +5,21 @@ import (
|
|||
)
|
||||
|
||||
type NodeConfig struct {
|
||||
Node NodeInfo `mapstructure:"node" yaml:"node"`
|
||||
Node NodeInfo `mapstructure:"node" yaml:"node"`
|
||||
Transport TransportConf `mapstructure:"transport" yaml:"transport"`
|
||||
TLS TLSConf `mapstructure:"tls" yaml:"tls"`
|
||||
Runtime RuntimeConf `mapstructure:"runtime" yaml:"runtime"`
|
||||
SQLite SQLiteConf `mapstructure:"sqlite" yaml:"sqlite"`
|
||||
Logging LoggingConf `mapstructure:"logging" yaml:"logging"`
|
||||
Metrics MetricsConf `mapstructure:"metrics" yaml:"metrics"`
|
||||
Adapters AdaptersConf `mapstructure:"adapters" yaml:"adapters"`
|
||||
TLS TLSConf `mapstructure:"tls" yaml:"tls"`
|
||||
Runtime RuntimeConf `mapstructure:"runtime" yaml:"runtime"`
|
||||
SQLite SQLiteConf `mapstructure:"sqlite" yaml:"sqlite"`
|
||||
Logging LoggingConf `mapstructure:"logging" yaml:"logging"`
|
||||
Metrics MetricsConf `mapstructure:"metrics" yaml:"metrics"`
|
||||
Adapters AdaptersConf `mapstructure:"adapters" yaml:"adapters"`
|
||||
}
|
||||
|
||||
type EdgeConfig struct {
|
||||
Server EdgeServerConf `mapstructure:"server" yaml:"server"`
|
||||
TLS TLSConf `mapstructure:"tls" yaml:"tls"`
|
||||
Logging LoggingConf `mapstructure:"logging" yaml:"logging"`
|
||||
Metrics MetricsConf `mapstructure:"metrics" yaml:"metrics"`
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
|
|
@ -20,6 +27,10 @@ type NodeInfo struct {
|
|||
Name string `mapstructure:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
type EdgeServerConf struct {
|
||||
Listen string `mapstructure:"listen" yaml:"listen"`
|
||||
}
|
||||
|
||||
type TransportConf struct {
|
||||
EdgeAddr string `mapstructure:"edge_addr" yaml:"edge_addr"`
|
||||
}
|
||||
|
|
@ -89,6 +100,20 @@ func Load(cfgFile string) (*NodeConfig, error) {
|
|||
return &cfg, nil
|
||||
}
|
||||
|
||||
func LoadEdge(cfgFile string) (*EdgeConfig, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(cfgFile)
|
||||
setEdgeDefaults(v)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg EdgeConfig
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("transport.edge_addr", "localhost:9090")
|
||||
v.SetDefault("runtime.concurrency", 4)
|
||||
|
|
@ -98,3 +123,10 @@ func setDefaults(v *viper.Viper) {
|
|||
v.SetDefault("metrics.port", 9091)
|
||||
v.SetDefault("tls.enabled", false)
|
||||
}
|
||||
|
||||
func setEdgeDefaults(v *viper.Viper) {
|
||||
v.SetDefault("server.listen", "0.0.0.0:9090")
|
||||
v.SetDefault("logging.level", "info")
|
||||
v.SetDefault("metrics.port", 9092)
|
||||
v.SetDefault("tls.enabled", false)
|
||||
}
|
||||
|
|
|
|||
296
proto/gen/iop/control.pb.go
Normal file
296
proto/gen/iop/control.pb.go
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v5.29.3
|
||||
// source: proto/iop/control.proto
|
||||
|
||||
package iop
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// PolicyRule defines a routing/resource constraint.
|
||||
type PolicyRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Expression string `protobuf:"bytes,2,opt,name=expression,proto3" json:"expression,omitempty"`
|
||||
Params map[string]string `protobuf:"bytes,3,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PolicyRule) Reset() {
|
||||
*x = PolicyRule{}
|
||||
mi := &file_proto_iop_control_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PolicyRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PolicyRule) ProtoMessage() {}
|
||||
|
||||
func (x *PolicyRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_control_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PolicyRule.ProtoReflect.Descriptor instead.
|
||||
func (*PolicyRule) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_control_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PolicyRule) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PolicyRule) GetExpression() string {
|
||||
if x != nil {
|
||||
return x.Expression
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PolicyRule) GetParams() map[string]string {
|
||||
if x != nil {
|
||||
return x.Params
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScheduleRequest asks the control-plane to route a job to a node.
|
||||
type ScheduleRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
|
||||
Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"`
|
||||
Policies []*PolicyRule `protobuf:"bytes,3,rep,name=policies,proto3" json:"policies,omitempty"`
|
||||
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) Reset() {
|
||||
*x = ScheduleRequest{}
|
||||
mi := &file_proto_iop_control_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ScheduleRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ScheduleRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_control_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ScheduleRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ScheduleRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_control_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) GetJobId() string {
|
||||
if x != nil {
|
||||
return x.JobId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) GetModel() string {
|
||||
if x != nil {
|
||||
return x.Model
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) GetPolicies() []*PolicyRule {
|
||||
if x != nil {
|
||||
return x.Policies
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ScheduleRequest) GetMetadata() map[string]string {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScheduleResponse contains the selected node address.
|
||||
type ScheduleResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScheduleResponse) Reset() {
|
||||
*x = ScheduleResponse{}
|
||||
mi := &file_proto_iop_control_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ScheduleResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ScheduleResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ScheduleResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_control_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ScheduleResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ScheduleResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_control_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ScheduleResponse) GetNodeId() string {
|
||||
if x != nil {
|
||||
return x.NodeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScheduleResponse) GetAddress() string {
|
||||
if x != nil {
|
||||
return x.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScheduleResponse) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_iop_control_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_iop_control_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x17proto/iop/control.proto\x12\x03iop\"\xb0\x01\n" +
|
||||
"\n" +
|
||||
"PolicyRule\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1e\n" +
|
||||
"\n" +
|
||||
"expression\x18\x02 \x01(\tR\n" +
|
||||
"expression\x123\n" +
|
||||
"\x06params\x18\x03 \x03(\v2\x1b.iop.PolicyRule.ParamsEntryR\x06params\x1a9\n" +
|
||||
"\vParamsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x01\n" +
|
||||
"\x0fScheduleRequest\x12\x15\n" +
|
||||
"\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x14\n" +
|
||||
"\x05model\x18\x02 \x01(\tR\x05model\x12+\n" +
|
||||
"\bpolicies\x18\x03 \x03(\v2\x0f.iop.PolicyRuleR\bpolicies\x12>\n" +
|
||||
"\bmetadata\x18\x04 \x03(\v2\".iop.ScheduleRequest.MetadataEntryR\bmetadata\x1a;\n" +
|
||||
"\rMetadataEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"[\n" +
|
||||
"\x10ScheduleResponse\x12\x17\n" +
|
||||
"\anode_id\x18\x01 \x01(\tR\x06nodeId\x12\x18\n" +
|
||||
"\aaddress\x18\x02 \x01(\tR\aaddress\x12\x14\n" +
|
||||
"\x05token\x18\x03 \x01(\tR\x05tokenB\x13Z\x11iop/proto/gen/iopb\x06proto3"
|
||||
|
||||
var (
|
||||
file_proto_iop_control_proto_rawDescOnce sync.Once
|
||||
file_proto_iop_control_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_iop_control_proto_rawDescGZIP() []byte {
|
||||
file_proto_iop_control_proto_rawDescOnce.Do(func() {
|
||||
file_proto_iop_control_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_iop_control_proto_rawDesc), len(file_proto_iop_control_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_iop_control_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_iop_control_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_proto_iop_control_proto_goTypes = []any{
|
||||
(*PolicyRule)(nil), // 0: iop.PolicyRule
|
||||
(*ScheduleRequest)(nil), // 1: iop.ScheduleRequest
|
||||
(*ScheduleResponse)(nil), // 2: iop.ScheduleResponse
|
||||
nil, // 3: iop.PolicyRule.ParamsEntry
|
||||
nil, // 4: iop.ScheduleRequest.MetadataEntry
|
||||
}
|
||||
var file_proto_iop_control_proto_depIdxs = []int32{
|
||||
3, // 0: iop.PolicyRule.params:type_name -> iop.PolicyRule.ParamsEntry
|
||||
0, // 1: iop.ScheduleRequest.policies:type_name -> iop.PolicyRule
|
||||
4, // 2: iop.ScheduleRequest.metadata:type_name -> iop.ScheduleRequest.MetadataEntry
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_iop_control_proto_init() }
|
||||
func file_proto_iop_control_proto_init() {
|
||||
if File_proto_iop_control_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_control_proto_rawDesc), len(file_proto_iop_control_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_iop_control_proto_goTypes,
|
||||
DependencyIndexes: file_proto_iop_control_proto_depIdxs,
|
||||
MessageInfos: file_proto_iop_control_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_iop_control_proto = out.File
|
||||
file_proto_iop_control_proto_goTypes = nil
|
||||
file_proto_iop_control_proto_depIdxs = nil
|
||||
}
|
||||
413
proto/gen/iop/job.pb.go
Normal file
413
proto/gen/iop/job.pb.go
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v5.29.3
|
||||
// source: proto/iop/job.proto
|
||||
|
||||
package iop
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type JobStatus int32
|
||||
|
||||
const (
|
||||
JobStatus_JOB_STATUS_UNSPECIFIED JobStatus = 0
|
||||
JobStatus_JOB_STATUS_PENDING JobStatus = 1
|
||||
JobStatus_JOB_STATUS_RUNNING JobStatus = 2
|
||||
JobStatus_JOB_STATUS_COMPLETED JobStatus = 3
|
||||
JobStatus_JOB_STATUS_FAILED JobStatus = 4
|
||||
JobStatus_JOB_STATUS_CANCELLED JobStatus = 5
|
||||
)
|
||||
|
||||
// Enum value maps for JobStatus.
|
||||
var (
|
||||
JobStatus_name = map[int32]string{
|
||||
0: "JOB_STATUS_UNSPECIFIED",
|
||||
1: "JOB_STATUS_PENDING",
|
||||
2: "JOB_STATUS_RUNNING",
|
||||
3: "JOB_STATUS_COMPLETED",
|
||||
4: "JOB_STATUS_FAILED",
|
||||
5: "JOB_STATUS_CANCELLED",
|
||||
}
|
||||
JobStatus_value = map[string]int32{
|
||||
"JOB_STATUS_UNSPECIFIED": 0,
|
||||
"JOB_STATUS_PENDING": 1,
|
||||
"JOB_STATUS_RUNNING": 2,
|
||||
"JOB_STATUS_COMPLETED": 3,
|
||||
"JOB_STATUS_FAILED": 4,
|
||||
"JOB_STATUS_CANCELLED": 5,
|
||||
}
|
||||
)
|
||||
|
||||
func (x JobStatus) Enum() *JobStatus {
|
||||
p := new(JobStatus)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x JobStatus) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (JobStatus) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_proto_iop_job_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (JobStatus) Type() protoreflect.EnumType {
|
||||
return &file_proto_iop_job_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x JobStatus) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobStatus.Descriptor instead.
|
||||
func (JobStatus) EnumDescriptor() ([]byte, []int) {
|
||||
return file_proto_iop_job_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
|
||||
RunId string `protobuf:"bytes,2,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
|
||||
NodeId string `protobuf:"bytes,3,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Adapter string `protobuf:"bytes,4,opt,name=adapter,proto3" json:"adapter,omitempty"`
|
||||
Model string `protobuf:"bytes,5,opt,name=model,proto3" json:"model,omitempty"`
|
||||
Status JobStatus `protobuf:"varint,6,opt,name=status,proto3,enum=iop.JobStatus" json:"status,omitempty"`
|
||||
CreatedAt int64 `protobuf:"varint,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
StartedAt int64 `protobuf:"varint,8,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"`
|
||||
FinishedAt int64 `protobuf:"varint,9,opt,name=finished_at,json=finishedAt,proto3" json:"finished_at,omitempty"`
|
||||
Error string `protobuf:"bytes,10,opt,name=error,proto3" json:"error,omitempty"`
|
||||
Metadata map[string]string `protobuf:"bytes,11,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Job) Reset() {
|
||||
*x = Job{}
|
||||
mi := &file_proto_iop_job_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Job) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Job) ProtoMessage() {}
|
||||
|
||||
func (x *Job) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_job_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Job.ProtoReflect.Descriptor instead.
|
||||
func (*Job) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_job_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Job) GetJobId() string {
|
||||
if x != nil {
|
||||
return x.JobId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetRunId() string {
|
||||
if x != nil {
|
||||
return x.RunId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetNodeId() string {
|
||||
if x != nil {
|
||||
return x.NodeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetAdapter() string {
|
||||
if x != nil {
|
||||
return x.Adapter
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetModel() string {
|
||||
if x != nil {
|
||||
return x.Model
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetStatus() JobStatus {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
}
|
||||
return JobStatus_JOB_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *Job) GetCreatedAt() int64 {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Job) GetStartedAt() int64 {
|
||||
if x != nil {
|
||||
return x.StartedAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Job) GetFinishedAt() int64 {
|
||||
if x != nil {
|
||||
return x.FinishedAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Job) GetError() string {
|
||||
if x != nil {
|
||||
return x.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Job) GetMetadata() map[string]string {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type JobListRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
StatusFilter JobStatus `protobuf:"varint,1,opt,name=status_filter,json=statusFilter,proto3,enum=iop.JobStatus" json:"status_filter,omitempty"`
|
||||
Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||
Cursor string `protobuf:"bytes,3,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *JobListRequest) Reset() {
|
||||
*x = JobListRequest{}
|
||||
mi := &file_proto_iop_job_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *JobListRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*JobListRequest) ProtoMessage() {}
|
||||
|
||||
func (x *JobListRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_job_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobListRequest.ProtoReflect.Descriptor instead.
|
||||
func (*JobListRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_job_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *JobListRequest) GetStatusFilter() JobStatus {
|
||||
if x != nil {
|
||||
return x.StatusFilter
|
||||
}
|
||||
return JobStatus_JOB_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *JobListRequest) GetLimit() int32 {
|
||||
if x != nil {
|
||||
return x.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *JobListRequest) GetCursor() string {
|
||||
if x != nil {
|
||||
return x.Cursor
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type JobListResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Jobs []*Job `protobuf:"bytes,1,rep,name=jobs,proto3" json:"jobs,omitempty"`
|
||||
Next string `protobuf:"bytes,2,opt,name=next,proto3" json:"next,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *JobListResponse) Reset() {
|
||||
*x = JobListResponse{}
|
||||
mi := &file_proto_iop_job_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *JobListResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*JobListResponse) ProtoMessage() {}
|
||||
|
||||
func (x *JobListResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_job_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use JobListResponse.ProtoReflect.Descriptor instead.
|
||||
func (*JobListResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_job_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *JobListResponse) GetJobs() []*Job {
|
||||
if x != nil {
|
||||
return x.Jobs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JobListResponse) GetNext() string {
|
||||
if x != nil {
|
||||
return x.Next
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_iop_job_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_iop_job_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13proto/iop/job.proto\x12\x03iop\"\x8a\x03\n" +
|
||||
"\x03Job\x12\x15\n" +
|
||||
"\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" +
|
||||
"\x06run_id\x18\x02 \x01(\tR\x05runId\x12\x17\n" +
|
||||
"\anode_id\x18\x03 \x01(\tR\x06nodeId\x12\x18\n" +
|
||||
"\aadapter\x18\x04 \x01(\tR\aadapter\x12\x14\n" +
|
||||
"\x05model\x18\x05 \x01(\tR\x05model\x12&\n" +
|
||||
"\x06status\x18\x06 \x01(\x0e2\x0e.iop.JobStatusR\x06status\x12\x1d\n" +
|
||||
"\n" +
|
||||
"created_at\x18\a \x01(\x03R\tcreatedAt\x12\x1d\n" +
|
||||
"\n" +
|
||||
"started_at\x18\b \x01(\x03R\tstartedAt\x12\x1f\n" +
|
||||
"\vfinished_at\x18\t \x01(\x03R\n" +
|
||||
"finishedAt\x12\x14\n" +
|
||||
"\x05error\x18\n" +
|
||||
" \x01(\tR\x05error\x122\n" +
|
||||
"\bmetadata\x18\v \x03(\v2\x16.iop.Job.MetadataEntryR\bmetadata\x1a;\n" +
|
||||
"\rMetadataEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"s\n" +
|
||||
"\x0eJobListRequest\x123\n" +
|
||||
"\rstatus_filter\x18\x01 \x01(\x0e2\x0e.iop.JobStatusR\fstatusFilter\x12\x14\n" +
|
||||
"\x05limit\x18\x02 \x01(\x05R\x05limit\x12\x16\n" +
|
||||
"\x06cursor\x18\x03 \x01(\tR\x06cursor\"C\n" +
|
||||
"\x0fJobListResponse\x12\x1c\n" +
|
||||
"\x04jobs\x18\x01 \x03(\v2\b.iop.JobR\x04jobs\x12\x12\n" +
|
||||
"\x04next\x18\x02 \x01(\tR\x04next*\xa2\x01\n" +
|
||||
"\tJobStatus\x12\x1a\n" +
|
||||
"\x16JOB_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" +
|
||||
"\x12JOB_STATUS_PENDING\x10\x01\x12\x16\n" +
|
||||
"\x12JOB_STATUS_RUNNING\x10\x02\x12\x18\n" +
|
||||
"\x14JOB_STATUS_COMPLETED\x10\x03\x12\x15\n" +
|
||||
"\x11JOB_STATUS_FAILED\x10\x04\x12\x18\n" +
|
||||
"\x14JOB_STATUS_CANCELLED\x10\x05B\x13Z\x11iop/proto/gen/iopb\x06proto3"
|
||||
|
||||
var (
|
||||
file_proto_iop_job_proto_rawDescOnce sync.Once
|
||||
file_proto_iop_job_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_iop_job_proto_rawDescGZIP() []byte {
|
||||
file_proto_iop_job_proto_rawDescOnce.Do(func() {
|
||||
file_proto_iop_job_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_iop_job_proto_rawDesc), len(file_proto_iop_job_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_iop_job_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_iop_job_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_proto_iop_job_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_proto_iop_job_proto_goTypes = []any{
|
||||
(JobStatus)(0), // 0: iop.JobStatus
|
||||
(*Job)(nil), // 1: iop.Job
|
||||
(*JobListRequest)(nil), // 2: iop.JobListRequest
|
||||
(*JobListResponse)(nil), // 3: iop.JobListResponse
|
||||
nil, // 4: iop.Job.MetadataEntry
|
||||
}
|
||||
var file_proto_iop_job_proto_depIdxs = []int32{
|
||||
0, // 0: iop.Job.status:type_name -> iop.JobStatus
|
||||
4, // 1: iop.Job.metadata:type_name -> iop.Job.MetadataEntry
|
||||
0, // 2: iop.JobListRequest.status_filter:type_name -> iop.JobStatus
|
||||
1, // 3: iop.JobListResponse.jobs:type_name -> iop.Job
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_iop_job_proto_init() }
|
||||
func file_proto_iop_job_proto_init() {
|
||||
if File_proto_iop_job_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_job_proto_rawDesc), len(file_proto_iop_job_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_iop_job_proto_goTypes,
|
||||
DependencyIndexes: file_proto_iop_job_proto_depIdxs,
|
||||
EnumInfos: file_proto_iop_job_proto_enumTypes,
|
||||
MessageInfos: file_proto_iop_job_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_iop_job_proto = out.File
|
||||
file_proto_iop_job_proto_goTypes = nil
|
||||
file_proto_iop_job_proto_depIdxs = nil
|
||||
}
|
||||
350
proto/gen/iop/node.pb.go
Normal file
350
proto/gen/iop/node.pb.go
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v5.29.3
|
||||
// source: proto/iop/node.proto
|
||||
|
||||
package iop
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type NodeStatus int32
|
||||
|
||||
const (
|
||||
NodeStatus_NODE_STATUS_UNSPECIFIED NodeStatus = 0
|
||||
NodeStatus_NODE_STATUS_ONLINE NodeStatus = 1
|
||||
NodeStatus_NODE_STATUS_OFFLINE NodeStatus = 2
|
||||
NodeStatus_NODE_STATUS_DRAINING NodeStatus = 3
|
||||
)
|
||||
|
||||
// Enum value maps for NodeStatus.
|
||||
var (
|
||||
NodeStatus_name = map[int32]string{
|
||||
0: "NODE_STATUS_UNSPECIFIED",
|
||||
1: "NODE_STATUS_ONLINE",
|
||||
2: "NODE_STATUS_OFFLINE",
|
||||
3: "NODE_STATUS_DRAINING",
|
||||
}
|
||||
NodeStatus_value = map[string]int32{
|
||||
"NODE_STATUS_UNSPECIFIED": 0,
|
||||
"NODE_STATUS_ONLINE": 1,
|
||||
"NODE_STATUS_OFFLINE": 2,
|
||||
"NODE_STATUS_DRAINING": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x NodeStatus) Enum() *NodeStatus {
|
||||
p := new(NodeStatus)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x NodeStatus) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (NodeStatus) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_proto_iop_node_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (NodeStatus) Type() protoreflect.EnumType {
|
||||
return &file_proto_iop_node_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x NodeStatus) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NodeStatus.Descriptor instead.
|
||||
func (NodeStatus) EnumDescriptor() ([]byte, []int) {
|
||||
return file_proto_iop_node_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// NodeInfo describes a registered node.
|
||||
type NodeInfo struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Status NodeStatus `protobuf:"varint,5,opt,name=status,proto3,enum=iop.NodeStatus" json:"status,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,6,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NodeInfo) Reset() {
|
||||
*x = NodeInfo{}
|
||||
mi := &file_proto_iop_node_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NodeInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NodeInfo) ProtoMessage() {}
|
||||
|
||||
func (x *NodeInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_node_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead.
|
||||
func (*NodeInfo) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_node_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetNodeId() string {
|
||||
if x != nil {
|
||||
return x.NodeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetAddress() string {
|
||||
if x != nil {
|
||||
return x.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetVersion() string {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetStatus() NodeStatus {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
}
|
||||
return NodeStatus_NODE_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *NodeInfo) GetLabels() map[string]string {
|
||||
if x != nil {
|
||||
return x.Labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeRegisterRequest is sent to the control-plane on startup.
|
||||
type NodeRegisterRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Info *NodeInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NodeRegisterRequest) Reset() {
|
||||
*x = NodeRegisterRequest{}
|
||||
mi := &file_proto_iop_node_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NodeRegisterRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NodeRegisterRequest) ProtoMessage() {}
|
||||
|
||||
func (x *NodeRegisterRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_node_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NodeRegisterRequest.ProtoReflect.Descriptor instead.
|
||||
func (*NodeRegisterRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_node_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *NodeRegisterRequest) GetInfo() *NodeInfo {
|
||||
if x != nil {
|
||||
return x.Info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeRegisterResponse is returned by the control-plane.
|
||||
type NodeRegisterResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
|
||||
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
|
||||
Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NodeRegisterResponse) Reset() {
|
||||
*x = NodeRegisterResponse{}
|
||||
mi := &file_proto_iop_node_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NodeRegisterResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NodeRegisterResponse) ProtoMessage() {}
|
||||
|
||||
func (x *NodeRegisterResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_node_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NodeRegisterResponse.ProtoReflect.Descriptor instead.
|
||||
func (*NodeRegisterResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_node_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *NodeRegisterResponse) GetAccepted() bool {
|
||||
if x != nil {
|
||||
return x.Accepted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NodeRegisterResponse) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NodeRegisterResponse) GetReason() string {
|
||||
if x != nil {
|
||||
return x.Reason
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_iop_node_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_iop_node_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14proto/iop/node.proto\x12\x03iop\"\x82\x02\n" +
|
||||
"\bNodeInfo\x12\x17\n" +
|
||||
"\anode_id\x18\x01 \x01(\tR\x06nodeId\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" +
|
||||
"\aaddress\x18\x03 \x01(\tR\aaddress\x12\x18\n" +
|
||||
"\aversion\x18\x04 \x01(\tR\aversion\x12'\n" +
|
||||
"\x06status\x18\x05 \x01(\x0e2\x0f.iop.NodeStatusR\x06status\x121\n" +
|
||||
"\x06labels\x18\x06 \x03(\v2\x19.iop.NodeInfo.LabelsEntryR\x06labels\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"8\n" +
|
||||
"\x13NodeRegisterRequest\x12!\n" +
|
||||
"\x04info\x18\x01 \x01(\v2\r.iop.NodeInfoR\x04info\"`\n" +
|
||||
"\x14NodeRegisterResponse\x12\x1a\n" +
|
||||
"\baccepted\x18\x01 \x01(\bR\baccepted\x12\x14\n" +
|
||||
"\x05token\x18\x02 \x01(\tR\x05token\x12\x16\n" +
|
||||
"\x06reason\x18\x03 \x01(\tR\x06reason*t\n" +
|
||||
"\n" +
|
||||
"NodeStatus\x12\x1b\n" +
|
||||
"\x17NODE_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" +
|
||||
"\x12NODE_STATUS_ONLINE\x10\x01\x12\x17\n" +
|
||||
"\x13NODE_STATUS_OFFLINE\x10\x02\x12\x18\n" +
|
||||
"\x14NODE_STATUS_DRAINING\x10\x03B\x13Z\x11iop/proto/gen/iopb\x06proto3"
|
||||
|
||||
var (
|
||||
file_proto_iop_node_proto_rawDescOnce sync.Once
|
||||
file_proto_iop_node_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_iop_node_proto_rawDescGZIP() []byte {
|
||||
file_proto_iop_node_proto_rawDescOnce.Do(func() {
|
||||
file_proto_iop_node_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_iop_node_proto_rawDesc), len(file_proto_iop_node_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_iop_node_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_iop_node_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_proto_iop_node_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_proto_iop_node_proto_goTypes = []any{
|
||||
(NodeStatus)(0), // 0: iop.NodeStatus
|
||||
(*NodeInfo)(nil), // 1: iop.NodeInfo
|
||||
(*NodeRegisterRequest)(nil), // 2: iop.NodeRegisterRequest
|
||||
(*NodeRegisterResponse)(nil), // 3: iop.NodeRegisterResponse
|
||||
nil, // 4: iop.NodeInfo.LabelsEntry
|
||||
}
|
||||
var file_proto_iop_node_proto_depIdxs = []int32{
|
||||
0, // 0: iop.NodeInfo.status:type_name -> iop.NodeStatus
|
||||
4, // 1: iop.NodeInfo.labels:type_name -> iop.NodeInfo.LabelsEntry
|
||||
1, // 2: iop.NodeRegisterRequest.info:type_name -> iop.NodeInfo
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_iop_node_proto_init() }
|
||||
func file_proto_iop_node_proto_init() {
|
||||
if File_proto_iop_node_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_node_proto_rawDesc), len(file_proto_iop_node_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_iop_node_proto_goTypes,
|
||||
DependencyIndexes: file_proto_iop_node_proto_depIdxs,
|
||||
EnumInfos: file_proto_iop_node_proto_enumTypes,
|
||||
MessageInfos: file_proto_iop_node_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_iop_node_proto = out.File
|
||||
file_proto_iop_node_proto_goTypes = nil
|
||||
file_proto_iop_node_proto_depIdxs = nil
|
||||
}
|
||||
686
proto/gen/iop/runtime.pb.go
Normal file
686
proto/gen/iop/runtime.pb.go
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v5.29.3
|
||||
// source: proto/iop/runtime.proto
|
||||
|
||||
package iop
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// RunRequest initiates a model execution.
|
||||
type RunRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
|
||||
Adapter string `protobuf:"bytes,2,opt,name=adapter,proto3" json:"adapter,omitempty"`
|
||||
Model string `protobuf:"bytes,3,opt,name=model,proto3" json:"model,omitempty"`
|
||||
Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"`
|
||||
Policy *structpb.Struct `protobuf:"bytes,5,opt,name=policy,proto3" json:"policy,omitempty"`
|
||||
Input *structpb.Struct `protobuf:"bytes,6,opt,name=input,proto3" json:"input,omitempty"`
|
||||
TimeoutSec int32 `protobuf:"varint,7,opt,name=timeout_sec,json=timeoutSec,proto3" json:"timeout_sec,omitempty"`
|
||||
Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RunRequest) Reset() {
|
||||
*x = RunRequest{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RunRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RunRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RunRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetRunId() string {
|
||||
if x != nil {
|
||||
return x.RunId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetAdapter() string {
|
||||
if x != nil {
|
||||
return x.Adapter
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetModel() string {
|
||||
if x != nil {
|
||||
return x.Model
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetWorkspace() string {
|
||||
if x != nil {
|
||||
return x.Workspace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetPolicy() *structpb.Struct {
|
||||
if x != nil {
|
||||
return x.Policy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetInput() *structpb.Struct {
|
||||
if x != nil {
|
||||
return x.Input
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetTimeoutSec() int32 {
|
||||
if x != nil {
|
||||
return x.TimeoutSec
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RunRequest) GetMetadata() map[string]string {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunEvent is a streaming execution event.
|
||||
type RunEvent struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
|
||||
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` // start | delta | complete | error
|
||||
Delta string `protobuf:"bytes,3,opt,name=delta,proto3" json:"delta,omitempty"`
|
||||
Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"`
|
||||
Usage *Usage `protobuf:"bytes,6,opt,name=usage,proto3" json:"usage,omitempty"`
|
||||
Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Timestamp int64 `protobuf:"varint,8,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // unix nano
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RunEvent) Reset() {
|
||||
*x = RunEvent{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RunEvent) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RunEvent) ProtoMessage() {}
|
||||
|
||||
func (x *RunEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RunEvent.ProtoReflect.Descriptor instead.
|
||||
func (*RunEvent) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetRunId() string {
|
||||
if x != nil {
|
||||
return x.RunId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetDelta() string {
|
||||
if x != nil {
|
||||
return x.Delta
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetError() string {
|
||||
if x != nil {
|
||||
return x.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetUsage() *Usage {
|
||||
if x != nil {
|
||||
return x.Usage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetMetadata() map[string]string {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RunEvent) GetTimestamp() int64 {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
InputTokens int32 `protobuf:"varint,1,opt,name=input_tokens,json=inputTokens,proto3" json:"input_tokens,omitempty"`
|
||||
OutputTokens int32 `protobuf:"varint,2,opt,name=output_tokens,json=outputTokens,proto3" json:"output_tokens,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Usage) Reset() {
|
||||
*x = Usage{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Usage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Usage) ProtoMessage() {}
|
||||
|
||||
func (x *Usage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Usage.ProtoReflect.Descriptor instead.
|
||||
func (*Usage) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Usage) GetInputTokens() int32 {
|
||||
if x != nil {
|
||||
return x.InputTokens
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Usage) GetOutputTokens() int32 {
|
||||
if x != nil {
|
||||
return x.OutputTokens
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Heartbeat is sent by both sides to keep the connection alive.
|
||||
type Heartbeat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Heartbeat) Reset() {
|
||||
*x = Heartbeat{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Heartbeat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Heartbeat) ProtoMessage() {}
|
||||
|
||||
func (x *Heartbeat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead.
|
||||
func (*Heartbeat) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *Heartbeat) GetTimestamp() int64 {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// CancelRequest asks the node to cancel a running execution.
|
||||
type CancelRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CancelRequest) Reset() {
|
||||
*x = CancelRequest{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CancelRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CancelRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CancelRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CancelRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *CancelRequest) GetRunId() string {
|
||||
if x != nil {
|
||||
return x.RunId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Error is returned when a request fails at the transport layer.
|
||||
type Error struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Error) Reset() {
|
||||
*x = Error{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Error) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Error) ProtoMessage() {}
|
||||
|
||||
func (x *Error) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Error.ProtoReflect.Descriptor instead.
|
||||
func (*Error) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Error) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Error) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CapabilityRequest asks the node what adapters/models it supports.
|
||||
type CapabilityRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CapabilityRequest) Reset() {
|
||||
*x = CapabilityRequest{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CapabilityRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CapabilityRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CapabilityRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CapabilityRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CapabilityRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
// CapabilityResponse lists the node's available adapters.
|
||||
type CapabilityResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Adapters []*AdapterInfo `protobuf:"bytes,2,rep,name=adapters,proto3" json:"adapters,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CapabilityResponse) Reset() {
|
||||
*x = CapabilityResponse{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CapabilityResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CapabilityResponse) ProtoMessage() {}
|
||||
|
||||
func (x *CapabilityResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CapabilityResponse.ProtoReflect.Descriptor instead.
|
||||
func (*CapabilityResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *CapabilityResponse) GetNodeId() string {
|
||||
if x != nil {
|
||||
return x.NodeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CapabilityResponse) GetAdapters() []*AdapterInfo {
|
||||
if x != nil {
|
||||
return x.Adapters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AdapterInfo struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Models []string `protobuf:"bytes,2,rep,name=models,proto3" json:"models,omitempty"`
|
||||
MaxConcurrency int32 `protobuf:"varint,3,opt,name=max_concurrency,json=maxConcurrency,proto3" json:"max_concurrency,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AdapterInfo) Reset() {
|
||||
*x = AdapterInfo{}
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AdapterInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AdapterInfo) ProtoMessage() {}
|
||||
|
||||
func (x *AdapterInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_iop_runtime_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AdapterInfo.ProtoReflect.Descriptor instead.
|
||||
func (*AdapterInfo) Descriptor() ([]byte, []int) {
|
||||
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *AdapterInfo) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AdapterInfo) GetModels() []string {
|
||||
if x != nil {
|
||||
return x.Models
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AdapterInfo) GetMaxConcurrency() int32 {
|
||||
if x != nil {
|
||||
return x.MaxConcurrency
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_proto_iop_runtime_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_iop_runtime_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x17proto/iop/runtime.proto\x12\x03iop\x1a\x1cgoogle/protobuf/struct.proto\"\xea\x02\n" +
|
||||
"\n" +
|
||||
"RunRequest\x12\x15\n" +
|
||||
"\x06run_id\x18\x01 \x01(\tR\x05runId\x12\x18\n" +
|
||||
"\aadapter\x18\x02 \x01(\tR\aadapter\x12\x14\n" +
|
||||
"\x05model\x18\x03 \x01(\tR\x05model\x12\x1c\n" +
|
||||
"\tworkspace\x18\x04 \x01(\tR\tworkspace\x12/\n" +
|
||||
"\x06policy\x18\x05 \x01(\v2\x17.google.protobuf.StructR\x06policy\x12-\n" +
|
||||
"\x05input\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x05input\x12\x1f\n" +
|
||||
"\vtimeout_sec\x18\a \x01(\x05R\n" +
|
||||
"timeoutSec\x129\n" +
|
||||
"\bmetadata\x18\b \x03(\v2\x1d.iop.RunRequest.MetadataEntryR\bmetadata\x1a;\n" +
|
||||
"\rMetadataEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb1\x02\n" +
|
||||
"\bRunEvent\x12\x15\n" +
|
||||
"\x06run_id\x18\x01 \x01(\tR\x05runId\x12\x12\n" +
|
||||
"\x04type\x18\x02 \x01(\tR\x04type\x12\x14\n" +
|
||||
"\x05delta\x18\x03 \x01(\tR\x05delta\x12\x18\n" +
|
||||
"\amessage\x18\x04 \x01(\tR\amessage\x12\x14\n" +
|
||||
"\x05error\x18\x05 \x01(\tR\x05error\x12 \n" +
|
||||
"\x05usage\x18\x06 \x01(\v2\n" +
|
||||
".iop.UsageR\x05usage\x127\n" +
|
||||
"\bmetadata\x18\a \x03(\v2\x1b.iop.RunEvent.MetadataEntryR\bmetadata\x12\x1c\n" +
|
||||
"\ttimestamp\x18\b \x01(\x03R\ttimestamp\x1a;\n" +
|
||||
"\rMetadataEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"O\n" +
|
||||
"\x05Usage\x12!\n" +
|
||||
"\finput_tokens\x18\x01 \x01(\x05R\vinputTokens\x12#\n" +
|
||||
"\routput_tokens\x18\x02 \x01(\x05R\foutputTokens\")\n" +
|
||||
"\tHeartbeat\x12\x1c\n" +
|
||||
"\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\"&\n" +
|
||||
"\rCancelRequest\x12\x15\n" +
|
||||
"\x06run_id\x18\x01 \x01(\tR\x05runId\"5\n" +
|
||||
"\x05Error\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\tR\x04code\x12\x18\n" +
|
||||
"\amessage\x18\x02 \x01(\tR\amessage\"\x13\n" +
|
||||
"\x11CapabilityRequest\"[\n" +
|
||||
"\x12CapabilityResponse\x12\x17\n" +
|
||||
"\anode_id\x18\x01 \x01(\tR\x06nodeId\x12,\n" +
|
||||
"\badapters\x18\x02 \x03(\v2\x10.iop.AdapterInfoR\badapters\"b\n" +
|
||||
"\vAdapterInfo\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
|
||||
"\x06models\x18\x02 \x03(\tR\x06models\x12'\n" +
|
||||
"\x0fmax_concurrency\x18\x03 \x01(\x05R\x0emaxConcurrencyB\x13Z\x11iop/proto/gen/iopb\x06proto3"
|
||||
|
||||
var (
|
||||
file_proto_iop_runtime_proto_rawDescOnce sync.Once
|
||||
file_proto_iop_runtime_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_iop_runtime_proto_rawDescGZIP() []byte {
|
||||
file_proto_iop_runtime_proto_rawDescOnce.Do(func() {
|
||||
file_proto_iop_runtime_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_iop_runtime_proto_rawDesc), len(file_proto_iop_runtime_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_iop_runtime_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_proto_iop_runtime_proto_goTypes = []any{
|
||||
(*RunRequest)(nil), // 0: iop.RunRequest
|
||||
(*RunEvent)(nil), // 1: iop.RunEvent
|
||||
(*Usage)(nil), // 2: iop.Usage
|
||||
(*Heartbeat)(nil), // 3: iop.Heartbeat
|
||||
(*CancelRequest)(nil), // 4: iop.CancelRequest
|
||||
(*Error)(nil), // 5: iop.Error
|
||||
(*CapabilityRequest)(nil), // 6: iop.CapabilityRequest
|
||||
(*CapabilityResponse)(nil), // 7: iop.CapabilityResponse
|
||||
(*AdapterInfo)(nil), // 8: iop.AdapterInfo
|
||||
nil, // 9: iop.RunRequest.MetadataEntry
|
||||
nil, // 10: iop.RunEvent.MetadataEntry
|
||||
(*structpb.Struct)(nil), // 11: google.protobuf.Struct
|
||||
}
|
||||
var file_proto_iop_runtime_proto_depIdxs = []int32{
|
||||
11, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct
|
||||
11, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct
|
||||
9, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry
|
||||
2, // 3: iop.RunEvent.usage:type_name -> iop.Usage
|
||||
10, // 4: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry
|
||||
8, // 5: iop.CapabilityResponse.adapters:type_name -> iop.AdapterInfo
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
6, // [6:6] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_iop_runtime_proto_init() }
|
||||
func file_proto_iop_runtime_proto_init() {
|
||||
if File_proto_iop_runtime_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_runtime_proto_rawDesc), len(file_proto_iop_runtime_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 11,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_iop_runtime_proto_goTypes,
|
||||
DependencyIndexes: file_proto_iop_runtime_proto_depIdxs,
|
||||
MessageInfos: file_proto_iop_runtime_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_iop_runtime_proto = out.File
|
||||
file_proto_iop_runtime_proto_goTypes = nil
|
||||
file_proto_iop_runtime_proto_depIdxs = nil
|
||||
}
|
||||
|
|
@ -2,39 +2,20 @@ syntax = "proto3";
|
|||
|
||||
package iop;
|
||||
|
||||
import "google/protobuf/struct.proto";
|
||||
|
||||
option go_package = "iop/proto/gen/iop";
|
||||
|
||||
// Envelope wraps every IOP TCP message.
|
||||
message Envelope {
|
||||
uint32 protocol_version = 1;
|
||||
string request_id = 2;
|
||||
string run_id = 3;
|
||||
string stream_id = 4;
|
||||
MessageType type = 5;
|
||||
bytes payload = 6; // serialized inner message
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
MESSAGE_TYPE_UNSPECIFIED = 0;
|
||||
MESSAGE_TYPE_RUN_REQUEST = 1;
|
||||
MESSAGE_TYPE_RUN_EVENT = 2;
|
||||
MESSAGE_TYPE_HEARTBEAT = 3;
|
||||
MESSAGE_TYPE_CANCEL_REQUEST = 4;
|
||||
MESSAGE_TYPE_ERROR = 5;
|
||||
MESSAGE_TYPE_CAPABILITY_REQUEST = 6;
|
||||
MESSAGE_TYPE_CAPABILITY_RESPONSE = 7;
|
||||
}
|
||||
|
||||
// RunRequest initiates a model execution.
|
||||
message RunRequest {
|
||||
string run_id = 1;
|
||||
string adapter = 2;
|
||||
string model = 3;
|
||||
string workspace = 4;
|
||||
bytes policy = 5; // JSON-encoded policy map
|
||||
bytes input = 6; // JSON-encoded input map
|
||||
int32 timeout_sec = 7;
|
||||
map<string, string> metadata = 8;
|
||||
string run_id = 1;
|
||||
string adapter = 2;
|
||||
string model = 3;
|
||||
string workspace = 4;
|
||||
google.protobuf.Struct policy = 5;
|
||||
google.protobuf.Struct input = 6;
|
||||
int32 timeout_sec = 7;
|
||||
map<string, string> metadata = 8;
|
||||
}
|
||||
|
||||
// RunEvent is a streaming execution event.
|
||||
|
|
|
|||
Loading…
Reference in a new issue