사용자별 credential 저장, lease, projection, runtime 전달과 OpenAI-compatible 계약 및 검증 근거를 함께 반영한다.
146 lines
7.1 KiB
Go
146 lines
7.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
edgenode "iop/apps/edge/internal/node"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
type fakeCredentialLeaseProvider struct {
|
|
valid bool
|
|
acquireCalls int
|
|
gotBinding *iop.CredentialLeaseBinding
|
|
mutateScope func(*iop.CredentialLeaseScope)
|
|
}
|
|
|
|
func (p *fakeCredentialLeaseProvider) ValidateCredentialBinding(binding *iop.CredentialLeaseBinding) error {
|
|
if !p.valid {
|
|
return errors.New("stale")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *fakeCredentialLeaseProvider) AcquireCredentialLease(_ context.Context, binding *iop.CredentialLeaseBinding, publicKey []byte) (*iop.SignedCredentialLease, error) {
|
|
p.acquireCalls++
|
|
p.gotBinding = binding
|
|
if len(publicKey) != 32 {
|
|
return nil, errors.New("bad recipient")
|
|
}
|
|
scope := &iop.CredentialLeaseScope{
|
|
LeaseId: "lease-test", PrincipalRef: binding.GetPrincipalRef(), CredentialSlotRef: binding.GetCredentialSlotRef(),
|
|
RouteId: binding.GetRouteId(), ProfileId: binding.GetProfileId(), UpstreamTarget: binding.GetUpstreamTarget(),
|
|
NodeId: binding.GetNodeId(), RecipientKeyId: binding.GetRecipientKeyId(), HeaderName: "Authorization", Scheme: "Bearer",
|
|
CredentialRevision: binding.GetCredentialRevision(), RouteRevision: binding.GetRouteRevision(),
|
|
ProjectionGeneration: binding.GetProjectionGeneration(),
|
|
}
|
|
if p.mutateScope != nil {
|
|
p.mutateScope(scope)
|
|
}
|
|
return &iop.SignedCredentialLease{Scope: scope}, nil
|
|
}
|
|
|
|
func TestAttachCredentialLeaseBindsSelectedCandidateAndUsesDedicatedFields(t *testing.T) {
|
|
provider := &fakeCredentialLeaseProvider{valid: true}
|
|
service := New(nil, nil)
|
|
service.SetCredentialLeaseProvider(provider)
|
|
service.SetCredentialLeaseLimit(1)
|
|
req := SubmitProviderTunnelRequest{CredentialBinding: &CredentialBinding{
|
|
PrincipalRef: "principal", CredentialSlotRef: "slot", RouteID: "route", ProfileID: "openai",
|
|
CredentialRevision: 7, RouteRevision: 8, ProjectionGeneration: 9,
|
|
}}
|
|
entry := &edgenode.NodeEntry{NodeID: "selected-node", CredentialRecipientKeyID: "recipient-key", CredentialRecipientPublicKey: make([]byte, 32)}
|
|
tunnel := &iop.ProviderTunnelRequest{Headers: map[string]string{"Content-Type": "application/json"}}
|
|
if err := service.attachCredentialLease(context.Background(), req, entry, "selected-target", tunnel); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer service.releaseCredentialLease()
|
|
if provider.acquireCalls != 1 || provider.gotBinding.GetNodeId() != "selected-node" || provider.gotBinding.GetUpstreamTarget() != "selected-target" || provider.gotBinding.GetRecipientKeyId() != "recipient-key" {
|
|
t.Fatalf("lease was not bound to selected candidate: %+v", provider.gotBinding)
|
|
}
|
|
if tunnel.GetCredentialLease() == nil || tunnel.GetCredentialBinding() == nil {
|
|
t.Fatal("dedicated credential fields were not attached")
|
|
}
|
|
for key, value := range tunnel.GetHeaders() {
|
|
if strings.Contains(strings.ToLower(key+value), "secret") || strings.EqualFold(key, "Authorization") {
|
|
t.Fatalf("generic header contains provider credential material: %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAttachCredentialLeaseFailsBeforeAcquireOnStaleBindingOrCollision(t *testing.T) {
|
|
provider := &fakeCredentialLeaseProvider{valid: false}
|
|
service := New(nil, nil)
|
|
service.SetCredentialLeaseProvider(provider)
|
|
service.SetCredentialLeaseLimit(1)
|
|
req := SubmitProviderTunnelRequest{CredentialBinding: &CredentialBinding{PrincipalRef: "p", CredentialSlotRef: "s", RouteID: "r", ProfileID: "openai", ProjectionGeneration: 1}}
|
|
entry := &edgenode.NodeEntry{NodeID: "node", CredentialRecipientKeyID: "key", CredentialRecipientPublicKey: make([]byte, 32)}
|
|
if err := service.attachCredentialLease(context.Background(), req, entry, "target", &iop.ProviderTunnelRequest{}); err == nil || provider.acquireCalls != 0 {
|
|
t.Fatalf("stale binding err=%v acquire_calls=%d", err, provider.acquireCalls)
|
|
}
|
|
provider.valid = true
|
|
if err := service.attachCredentialLease(context.Background(), req, entry, "target", &iop.ProviderTunnelRequest{Headers: map[string]string{"authorization": "caller-value"}}); err == nil || !strings.Contains(err.Error(), "collision") {
|
|
t.Fatalf("collision err=%v", err)
|
|
}
|
|
if provider.acquireCalls != 1 {
|
|
t.Fatalf("collision acquisition calls=%d, want 1 signed-header check", provider.acquireCalls)
|
|
}
|
|
}
|
|
|
|
func TestAttachCredentialLeaseRejectsMismatchedReturnedScope(t *testing.T) {
|
|
provider := &fakeCredentialLeaseProvider{valid: true, mutateScope: func(scope *iop.CredentialLeaseScope) {
|
|
scope.CredentialSlotRef = "different-slot"
|
|
}}
|
|
service := New(nil, nil)
|
|
service.SetCredentialLeaseProvider(provider)
|
|
service.SetCredentialLeaseLimit(1)
|
|
req := SubmitProviderTunnelRequest{CredentialBinding: &CredentialBinding{
|
|
PrincipalRef: "principal", CredentialSlotRef: "slot", RouteID: "route", ProfileID: "openai",
|
|
CredentialRevision: 7, RouteRevision: 8, ProjectionGeneration: 9,
|
|
}}
|
|
entry := &edgenode.NodeEntry{NodeID: "node", CredentialRecipientKeyID: "key", CredentialRecipientPublicKey: make([]byte, 32)}
|
|
err := service.attachCredentialLease(context.Background(), req, entry, "target", &iop.ProviderTunnelRequest{})
|
|
if err == nil || !strings.Contains(err.Error(), "scope mismatch") {
|
|
t.Fatalf("mismatched lease scope error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSubmitProviderPoolTunnelAttachesCredentialLeaseAfterCandidateSelection(t *testing.T) {
|
|
env := newProviderTunnelTestEnv(t)
|
|
provider := &fakeCredentialLeaseProvider{valid: true}
|
|
env.svc.SetCredentialLeaseProvider(provider)
|
|
env.svc.SetCredentialLeaseLimit(1)
|
|
|
|
result, err := env.svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
|
|
Run: SubmitRunRequest{ModelGroupKey: "qwen3.6:35b", ProviderPool: true},
|
|
Tunnel: SubmitProviderTunnelRequest{
|
|
CredentialBinding: &CredentialBinding{
|
|
PrincipalRef: "principal", CredentialSlotRef: "slot", RouteID: "route", ProfileID: "openai",
|
|
CredentialRevision: 7, RouteRevision: 8, ProjectionGeneration: 9,
|
|
},
|
|
Method: "POST", Path: "/v1/chat/completions", Operation: "chat_completions",
|
|
BuildBody: func(string) ([]byte, error) { return []byte(`{"model":"served-qwen"}`), nil },
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SubmitProviderPool: %v", err)
|
|
}
|
|
defer result.Tunnel.Close()
|
|
waitForCondition(t, func() bool { return env.capturedRequest() != nil }, "fake node did not receive managed provider-pool tunnel")
|
|
captured := env.capturedRequest()
|
|
if captured.GetCredentialLease() == nil || captured.GetCredentialBinding() == nil {
|
|
t.Fatal("managed provider-pool tunnel omitted dedicated credential fields")
|
|
}
|
|
if provider.acquireCalls != 1 || provider.gotBinding.GetNodeId() != "node-pool" || provider.gotBinding.GetRecipientKeyId() != "recipient-pool" || provider.gotBinding.GetUpstreamTarget() != "served-qwen" {
|
|
t.Fatalf("lease acquisition did not use the selected candidate: %+v", provider.gotBinding)
|
|
}
|
|
if got := result.DispatchInfo; got.CredentialSlotRef != "slot" || got.CredentialRevision != 7 {
|
|
t.Fatalf("dispatch credential attribution = %q/%d, want slot/7", got.CredentialSlotRef, got.CredentialRevision)
|
|
}
|
|
if got := result.Tunnel.Dispatch(); got.CredentialSlotRef != "slot" || got.CredentialRevision != 7 {
|
|
t.Fatalf("tunnel credential attribution = %q/%d, want slot/7", got.CredentialSlotRef, got.CredentialRevision)
|
|
}
|
|
}
|