2506 lines
83 KiB
Go
2506 lines
83 KiB
Go
package cluster
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/example/remote-access-platform/backend/internal/platform/clusterauth"
|
|
"github.com/example/remote-access-platform/backend/internal/platform/secrets"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func TestHashJoinTokenDoesNotStoreRawToken(t *testing.T) {
|
|
raw := "rap_join_example"
|
|
hashed, err := hashJoinToken(raw)
|
|
if err != nil {
|
|
t.Fatalf("hash join token: %v", err)
|
|
}
|
|
if hashed == raw {
|
|
t.Fatal("hash must not equal raw token")
|
|
}
|
|
if got, wantPrefix := hashed[:len(joinTokenHashPrefix)], joinTokenHashPrefix; got != wantPrefix {
|
|
t.Fatalf("hash prefix = %q, want %q", got, wantPrefix)
|
|
}
|
|
hashedAgain, err := hashJoinToken(raw)
|
|
if err != nil {
|
|
t.Fatalf("hash join token again: %v", err)
|
|
}
|
|
if hashed != hashedAgain {
|
|
t.Fatal("hash must be deterministic")
|
|
}
|
|
}
|
|
|
|
func TestClusterAuthorityPrivateKeyEncodingUsesSecretEncryptor(t *testing.T) {
|
|
encryptor, err := secrets.NewEncryptor("MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=", "test-key")
|
|
if err != nil {
|
|
t.Fatalf("NewEncryptor: %v", err)
|
|
}
|
|
store := (&PostgresStore{}).WithClusterKeyEncryptor(encryptor)
|
|
|
|
encoded, err := store.encodeClusterAuthorityPrivateKey("cluster-1", "private-key")
|
|
if err != nil {
|
|
t.Fatalf("encodeClusterAuthorityPrivateKey: %v", err)
|
|
}
|
|
if encoded == "private-key" || !strings.HasPrefix(encoded, encryptedClusterAuthorityKeyPrefix) {
|
|
t.Fatalf("private key was not encrypted: %q", encoded)
|
|
}
|
|
decoded, err := store.decodeClusterAuthorityPrivateKey("cluster-1", encoded)
|
|
if err != nil {
|
|
t.Fatalf("decodeClusterAuthorityPrivateKey: %v", err)
|
|
}
|
|
if decoded != "private-key" {
|
|
t.Fatalf("decoded private key = %q", decoded)
|
|
}
|
|
if _, err := store.decodeClusterAuthorityPrivateKey("cluster-2", encoded); err == nil {
|
|
t.Fatal("expected wrong cluster AAD to fail")
|
|
}
|
|
}
|
|
|
|
func TestCreateJoinTokenRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateJoinToken(context.Background(), CreateJoinTokenInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateJoinTokenStoresHashOnlyAndReturnsRawOnce(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
service.now = func() time.Time { return time.Date(2026, 4, 26, 12, 0, 0, 0, time.UTC) }
|
|
|
|
created, err := service.CreateJoinToken(context.Background(), CreateJoinTokenInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Scope: json.RawMessage(`{"roles":["rdp-worker"]}`),
|
|
MaxUses: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create join token: %v", err)
|
|
}
|
|
if created.Token == "" {
|
|
t.Fatal("raw token must be returned to caller once")
|
|
}
|
|
if store.lastTokenHash == "" || store.lastTokenHash == created.Token {
|
|
t.Fatalf("stored token hash = %q, raw token = %q", store.lastTokenHash, created.Token)
|
|
}
|
|
if created.AuthoritySignature == nil || len(created.AuthorityPayload) == 0 {
|
|
t.Fatalf("created token missing authority signature: %+v", created.NodeJoinToken)
|
|
}
|
|
if err := clusterauth.VerifyRaw(store.clusterAuthority.PublicKey, created.AuthorityPayload, *created.AuthoritySignature); err != nil {
|
|
t.Fatalf("verify token authority signature: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUpdateClusterRequiresMutableAuthority(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
authorityState: ClusterAuthorityState{
|
|
ClusterID: "cluster-1",
|
|
AuthorityState: "minority",
|
|
MutationMode: "read_only",
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.UpdateCluster(context.Background(), UpdateClusterInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "Cluster One",
|
|
Status: ClusterStatusActive,
|
|
Metadata: json.RawMessage(`{}`),
|
|
})
|
|
if !errors.Is(err, ErrClusterReadOnly) {
|
|
t.Fatalf("err = %v, want ErrClusterReadOnly", err)
|
|
}
|
|
}
|
|
|
|
func TestUpdateClusterValidatesStatusAndMetadata(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.UpdateCluster(context.Background(), UpdateClusterInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "Cluster One",
|
|
Status: "unknown",
|
|
Metadata: json.RawMessage(`{}`),
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
|
|
_, err = service.UpdateCluster(context.Background(), UpdateClusterInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "Cluster One",
|
|
Status: ClusterStatusActive,
|
|
Metadata: json.RawMessage(`{`),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "metadata") {
|
|
t.Fatalf("err = %v, want metadata validation error", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateNodeGroupValidatesNameAndMetadata(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateNodeGroup(context.Background(), CreateNodeGroupInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: " ",
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
|
|
_, err = service.CreateNodeGroup(context.Background(), CreateNodeGroupInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "DC-1",
|
|
Metadata: json.RawMessage(`{`),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "metadata") {
|
|
t.Fatalf("err = %v, want metadata validation error", err)
|
|
}
|
|
}
|
|
|
|
func TestAssignNodeToGroupPreservesConcreteMembership(t *testing.T) {
|
|
groupID := "group-1"
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
node, err := service.AssignNodeToGroup(context.Background(), AssignNodeGroupInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
GroupID: &groupID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("assign node group: %v", err)
|
|
}
|
|
if node.ID != "node-1" || node.NodeGroupID == nil || *node.NodeGroupID != groupID {
|
|
t.Fatalf("unexpected node group assignment: %+v", node)
|
|
}
|
|
if store.lastAssignGroupInput.NodeID != "node-1" || store.lastAssignGroupInput.GroupID == nil {
|
|
t.Fatalf("assignment input not preserved: %+v", store.lastAssignGroupInput)
|
|
}
|
|
}
|
|
|
|
func TestCreateFabricEntryPointValidatesControlPlanePayload(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateFabricEntryPoint(context.Background(), CreateFabricEntryPointInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: " ",
|
|
EndpointType: "client_access",
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
|
|
_, err = service.CreateFabricEntryPoint(context.Background(), CreateFabricEntryPointInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "Main Entry",
|
|
EndpointType: "client_access",
|
|
Policy: json.RawMessage(`{`),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "valid json") {
|
|
t.Fatalf("err = %v, want json validation error", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateFabricEgressPoolDefaultsAndAudits(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
item, err := service.CreateFabricEgressPool(context.Background(), CreateFabricEgressPoolInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "Office Moscow",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create egress pool: %v", err)
|
|
}
|
|
if item.Status != "active" || string(item.RouteScope) != "{}" {
|
|
t.Fatalf("unexpected egress pool defaults: %+v", item)
|
|
}
|
|
if len(store.auditEvents) == 0 || store.auditEvents[len(store.auditEvents)-1].EventType != "fabric.egress_pool.created" {
|
|
t.Fatalf("missing egress pool audit event: %+v", store.auditEvents)
|
|
}
|
|
}
|
|
|
|
func TestAssignNodeRoleRejectsUnknownRole(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AssignNodeRole(context.Background(), AssignNodeRoleInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
Role: "can_run_rdp_worker",
|
|
})
|
|
if !errors.Is(err, ErrInvalidNodeRole) {
|
|
t.Fatalf("err = %v, want ErrInvalidNodeRole", err)
|
|
}
|
|
}
|
|
|
|
func TestAttachExistingNodeRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AttachExistingNodeToCluster(context.Background(), AttachExistingNodeInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestAttachExistingNodeRejectsUnknownRole(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AttachExistingNodeToCluster(context.Background(), AttachExistingNodeInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
Roles: []string{"can_run_rdp_worker"},
|
|
})
|
|
if !errors.Is(err, ErrInvalidNodeRole) {
|
|
t.Fatalf("err = %v, want ErrInvalidNodeRole", err)
|
|
}
|
|
}
|
|
|
|
func TestAttachExistingNodeUsesConcreteNodeAndRoles(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
node, err := service.AttachExistingNodeToCluster(context.Background(), AttachExistingNodeInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
Roles: []string{"entry-node", "rdp-worker"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("attach existing node: %v", err)
|
|
}
|
|
if node.ID != "node-1" || node.MembershipStatus != "active" {
|
|
t.Fatalf("unexpected node: %+v", node)
|
|
}
|
|
if store.lastAttachInput.NodeID != "node-1" || len(store.lastAttachInput.Roles) != 2 {
|
|
t.Fatalf("attach input not preserved: %+v", store.lastAttachInput)
|
|
}
|
|
}
|
|
|
|
func TestCreateJoinRequestRejectsExpiredOrRevokedToken(t *testing.T) {
|
|
store := &fakeRepository{validTokenErr: ErrInvalidJoinToken}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateJoinRequest(context.Background(), CreateJoinRequestInput{
|
|
ClusterID: "cluster-1",
|
|
JoinToken: "rap_join_invalid",
|
|
NodeName: "node-a",
|
|
NodeFingerprint: "fingerprint-a",
|
|
PublicKey: "public-key",
|
|
})
|
|
if !errors.Is(err, ErrInvalidJoinToken) {
|
|
t.Fatalf("err = %v, want ErrInvalidJoinToken", err)
|
|
}
|
|
}
|
|
|
|
func TestRevokeJoinTokenRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.RevokeJoinToken(context.Background(), RevokeJoinTokenInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
TokenID: "token-1",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestApproveJoinRequestReturnsBootstrapContract(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
approved, err := service.ApproveJoinRequest(context.Background(), ApproveJoinRequestInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
JoinRequestID: "join-request-1",
|
|
NodeKey: "node-key-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("approve join request: %v", err)
|
|
}
|
|
if approved.Bootstrap.ClusterID != "cluster-1" || approved.Bootstrap.IdentityStatus == "" {
|
|
t.Fatalf("unexpected bootstrap contract: %+v", approved.Bootstrap)
|
|
}
|
|
if approved.Bootstrap.ClusterAuthority == nil || approved.Bootstrap.AuthoritySignature == nil || len(approved.Bootstrap.AuthorityPayload) == 0 {
|
|
t.Fatalf("bootstrap missing authority contract: %+v", approved.Bootstrap)
|
|
}
|
|
if err := clusterauth.VerifyRaw(store.clusterAuthority.PublicKey, approved.Bootstrap.AuthorityPayload, *approved.Bootstrap.AuthoritySignature); err != nil {
|
|
t.Fatalf("verify approval authority signature: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGetJoinRequestBootstrapReturnsSignedApproval(t *testing.T) {
|
|
nodeID := "node-1"
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
bootstrapJoinRequest: NodeJoinRequest{
|
|
ID: "join-request-1",
|
|
ClusterID: "cluster-1",
|
|
NodeFingerprint: "node-fp",
|
|
PublicKey: "node-public-key",
|
|
Status: JoinRequestStatusApproved,
|
|
ApprovedNodeID: &nodeID,
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
result, err := service.GetJoinRequestBootstrap(context.Background(), GetJoinRequestBootstrapInput{
|
|
ClusterID: "cluster-1",
|
|
JoinRequestID: "join-request-1",
|
|
NodeFingerprint: "node-fp",
|
|
PublicKey: "node-public-key",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get join request bootstrap: %v", err)
|
|
}
|
|
if result.Bootstrap == nil || result.Bootstrap.NodeID != nodeID || result.Bootstrap.ClusterAuthority == nil {
|
|
t.Fatalf("unexpected bootstrap result: %+v", result)
|
|
}
|
|
if result.Bootstrap.AuthoritySignature == nil || len(result.Bootstrap.AuthorityPayload) == 0 {
|
|
t.Fatalf("bootstrap missing authority signature: %+v", result.Bootstrap)
|
|
}
|
|
if err := clusterauth.VerifyRaw(store.clusterAuthority.PublicKey, result.Bootstrap.AuthorityPayload, *result.Bootstrap.AuthoritySignature); err != nil {
|
|
t.Fatalf("verify bootstrap authority signature: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSetDesiredWorkloadRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.SetDesiredWorkload(context.Background(), SetDesiredWorkloadInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
ServiceType: "rdp-worker",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestReportWorkloadStatusDefaultsToSafeStubState(t *testing.T) {
|
|
store := &fakeRepository{}
|
|
service := NewService(store)
|
|
|
|
status, err := service.ReportWorkloadStatus(context.Background(), ReportWorkloadStatusInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
ServiceType: "rdp-worker",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("report workload status: %v", err)
|
|
}
|
|
if status.ReportedState != "unknown" || status.RuntimeMode != "container" {
|
|
t.Fatalf("unexpected status defaults: %+v", status)
|
|
}
|
|
}
|
|
|
|
func TestReportMeshLinkDoesNotRequirePlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{}
|
|
service := NewService(store)
|
|
|
|
link, err := service.ReportMeshLink(context.Background(), ReportMeshLinkInput{
|
|
ClusterID: "cluster-1",
|
|
SourceNodeID: "node-a",
|
|
TargetNodeID: "node-b",
|
|
LinkStatus: "reachable",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("report mesh link: %v", err)
|
|
}
|
|
if link.LinkStatus != "reachable" {
|
|
t.Fatalf("LinkStatus = %q", link.LinkStatus)
|
|
}
|
|
}
|
|
|
|
func TestCreateRouteIntentRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateRouteIntent(context.Background(), CreateRouteIntentInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
ServiceClass: "input",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigRequiresTestingFlag(t *testing.T) {
|
|
service := NewService(&fakeRepository{})
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if cfg.Enabled {
|
|
t.Fatal("config must be disabled when synthetic testing flag is off")
|
|
}
|
|
if len(cfg.Routes) != 0 || len(cfg.PeerEndpoints) != 0 {
|
|
t.Fatalf("disabled config must not leak topology: %+v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigIsNodeScoped(t *testing.T) {
|
|
now := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC)
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-r", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control"],
|
|
"peer_endpoints": {
|
|
"node-r": "http://node-r:19000",
|
|
"node-b": "http://node-b:19000",
|
|
"node-y": "http://node-y:19000"
|
|
},
|
|
"peer_endpoint_candidates": {
|
|
"node-r": [
|
|
{
|
|
"endpoint_id": "node-r-public",
|
|
"node_id": "node-r",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "203.0.113.10:443",
|
|
"address_family": "ipv4",
|
|
"reachability": "public",
|
|
"nat_type": "none",
|
|
"connectivity_mode": "direct",
|
|
"region": "eu",
|
|
"priority": 10,
|
|
"policy_tags": ["fast-path"],
|
|
"metadata": {"source":"test"}
|
|
}
|
|
],
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 20
|
|
}
|
|
]
|
|
},
|
|
"recovery_seeds": [
|
|
{
|
|
"node_id": "node-r",
|
|
"endpoint": "https://node-r.example.test:443",
|
|
"transport": "direct_tcp_tls",
|
|
"connectivity_mode": "direct",
|
|
"region": "eu",
|
|
"priority": 10,
|
|
"metadata": {"role":"stable-recovery"}
|
|
},
|
|
{
|
|
"node_id": "node-seed",
|
|
"endpoint": "wss://seed.example.test/mesh",
|
|
"transport": "wss",
|
|
"connectivity_mode": "direct",
|
|
"priority": 20
|
|
}
|
|
],
|
|
"route_version": "route-v1",
|
|
"policy_version": "policy-v1",
|
|
"peer_directory_version": "peers-v1"
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
{
|
|
ID: "route-x-y",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-x"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-y"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-x", "node-y"],
|
|
"peer_endpoints": {"node-y": "http://node-y:19000"}
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if !cfg.Enabled {
|
|
t.Fatal("config should be enabled")
|
|
}
|
|
if len(cfg.Routes) != 1 || cfg.Routes[0].RouteID != "route-a-b" {
|
|
t.Fatalf("routes = %+v", cfg.Routes)
|
|
}
|
|
if cfg.PeerEndpoints["node-r"] == "" || cfg.PeerEndpoints["node-b"] == "" {
|
|
t.Fatalf("peer endpoints missing: %+v", cfg.PeerEndpoints)
|
|
}
|
|
if _, leaked := cfg.PeerEndpoints["node-y"]; leaked {
|
|
t.Fatalf("unrelated topology leaked: %+v", cfg.PeerEndpoints)
|
|
}
|
|
nodeRCandidates := cfg.PeerEndpointCandidates["node-r"]
|
|
if len(nodeRCandidates) != 1 {
|
|
t.Fatalf("node-r candidates = %+v", cfg.PeerEndpointCandidates)
|
|
}
|
|
if got := nodeRCandidates[0]; got.EndpointID != "node-r-public" ||
|
|
got.Transport != "direct_tcp_tls" ||
|
|
got.Reachability != "public" ||
|
|
got.NATType != "none" ||
|
|
got.ConnectivityMode != "direct" ||
|
|
got.Priority != 10 {
|
|
t.Fatalf("unexpected node-r candidate: %+v", got)
|
|
}
|
|
if _, leaked := cfg.PeerEndpointCandidates["node-y"]; leaked {
|
|
t.Fatalf("unrelated candidate topology leaked: %+v", cfg.PeerEndpointCandidates)
|
|
}
|
|
if len(cfg.RecoverySeeds) != 2 || cfg.RecoverySeeds[0].NodeID != "node-r" || cfg.RecoverySeeds[1].NodeID != "node-seed" {
|
|
t.Fatalf("unexpected recovery seeds: %+v", cfg.RecoverySeeds)
|
|
}
|
|
nodeRDirectory, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-r")
|
|
if !ok || nodeRDirectory.CandidateCount != 1 || !nodeRDirectory.RecoverySeed {
|
|
t.Fatalf("node-r peer directory missing recovery/candidate metadata: %+v", cfg.PeerDirectory)
|
|
}
|
|
if _, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-a"); ok {
|
|
t.Fatalf("local node leaked into peer directory: %+v", cfg.PeerDirectory)
|
|
}
|
|
if _, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-y"); ok {
|
|
t.Fatalf("unrelated node leaked into peer directory: %+v", cfg.PeerDirectory)
|
|
}
|
|
if cfg.ProductionForwarding {
|
|
t.Fatal("production forwarding must remain false")
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigUsesReportedMeshEndpoint(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-b"]
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
heartbeats: map[string][]NodeHeartbeat{
|
|
"node-b": {
|
|
{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-b",
|
|
Metadata: json.RawMessage(`{
|
|
"mesh_endpoint_report": {
|
|
"schema_version": "c17z6.mesh_endpoint_report.v1",
|
|
"cluster_id": "cluster-1",
|
|
"node_id": "node-b",
|
|
"peer_endpoint": "https://node-b.dynamic.example.test:443",
|
|
"transport": "direct_tcp_tls",
|
|
"connectivity_mode": "direct",
|
|
"nat_type": "none",
|
|
"endpoint_candidates": [
|
|
{
|
|
"endpoint_id": "node-b-dynamic",
|
|
"node_id": "node-b",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "https://node-b.dynamic.example.test:443",
|
|
"reachability": "public",
|
|
"connectivity_mode": "direct",
|
|
"nat_type": "none",
|
|
"priority": 1,
|
|
"metadata": {"source":"heartbeat"}
|
|
}
|
|
]
|
|
}
|
|
}`),
|
|
ObservedAt: now,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if cfg.PeerEndpoints["node-b"] != "https://node-b.dynamic.example.test:443" {
|
|
t.Fatalf("reported endpoint not projected: %+v", cfg.PeerEndpoints)
|
|
}
|
|
if got := cfg.PeerEndpointCandidates["node-b"]; len(got) != 1 || got[0].EndpointID != "node-b-dynamic" {
|
|
t.Fatalf("reported candidates not projected: %+v", cfg.PeerEndpointCandidates)
|
|
}
|
|
entry, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-b")
|
|
if !ok || entry.EndpointCount != 1 || entry.CandidateCount != 1 {
|
|
t.Fatalf("peer directory did not include reported endpoint/candidate: %+v", cfg.PeerDirectory)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigIssuesRendezvousRelayLeases(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC)
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-r", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control", "service_payload"],
|
|
"peer_endpoints": {
|
|
"node-r": "http://node-r:19000"
|
|
},
|
|
"peer_endpoint_candidates": {
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 20
|
|
}
|
|
]
|
|
},
|
|
"rendezvous_leases": [
|
|
{
|
|
"peer_node_id": "node-b",
|
|
"relay_node_id": "node-r",
|
|
"relay_endpoint": "http://node-r:19000",
|
|
"priority": 5
|
|
}
|
|
]
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
{
|
|
ID: "route-x-y",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-x"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-y"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-x", "node-y"],
|
|
"peer_endpoints": {"node-x": "http://node-x:19000"},
|
|
"rendezvous_leases": [
|
|
{
|
|
"peer_node_id": "node-y",
|
|
"relay_node_id": "node-x",
|
|
"relay_endpoint": "http://node-x:19000"
|
|
}
|
|
]
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if cfg.SchemaVersion != "c17z18.synthetic.v1" {
|
|
t.Fatalf("schema version = %s, want c17z18.synthetic.v1", cfg.SchemaVersion)
|
|
}
|
|
if len(cfg.RendezvousLeases) != 1 {
|
|
t.Fatalf("unexpected rendezvous leases: %+v", cfg.RendezvousLeases)
|
|
}
|
|
lease := cfg.RendezvousLeases[0]
|
|
if lease.LeaseID != "route-a-b-rv-node-b-via-node-r" ||
|
|
lease.PeerNodeID != "node-b" ||
|
|
lease.RelayNodeID != "node-r" ||
|
|
lease.RelayEndpoint != "http://node-r:19000" ||
|
|
lease.Transport != "relay_control" ||
|
|
lease.Priority != 5 ||
|
|
!lease.ControlPlaneOnly ||
|
|
!containsString(lease.AllowedChannels, "fabric_control") ||
|
|
containsString(lease.AllowedChannels, "service_payload") {
|
|
t.Fatalf("unexpected rendezvous lease contract: %+v", lease)
|
|
}
|
|
if _, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-y"); ok {
|
|
t.Fatalf("unrelated rendezvous lease leaked into peer directory: %+v", cfg.PeerDirectory)
|
|
}
|
|
nodeB, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-b")
|
|
if !ok || !containsString(nodeB.ConnectivityModes, "relay_required") {
|
|
t.Fatalf("peer directory missing rendezvous peer mode: %+v", cfg.PeerDirectory)
|
|
}
|
|
nodeR, ok := findPeerDirectoryEntry(cfg.PeerDirectory, "node-r")
|
|
if !ok || !containsString(nodeR.ConnectivityModes, "relay_control") {
|
|
t.Fatalf("peer directory missing relay control mode: %+v", cfg.PeerDirectory)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigReplacesStaleRendezvousRelay(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 30, 0, 0, time.UTC)
|
|
staleHeartbeatMetadata, err := json.Marshal(map[string]any{
|
|
"mesh_rendezvous_lease_report": map[string]any{
|
|
"schema_version": "c17z18.mesh_rendezvous_lease_report.v1",
|
|
"cluster_id": "cluster-1",
|
|
"node_id": "node-a",
|
|
"observed_at": now.Format(time.RFC3339Nano),
|
|
"leases": []map[string]any{
|
|
{
|
|
"lease_id": "route-a-b-rv-node-b-via-node-r-old",
|
|
"peer_node_id": "node-b",
|
|
"relay_node_id": "node-r-old",
|
|
"route_ids": []string{"route-a-b"},
|
|
"stale_relay": true,
|
|
"reselection_needed": true,
|
|
"connection_state": "degraded",
|
|
"reason": "auto_outbound_only",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("marshal heartbeat metadata: %v", err)
|
|
}
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
heartbeats: map[string][]NodeHeartbeat{
|
|
"node-a": {
|
|
{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
Metadata: staleHeartbeatMetadata,
|
|
ObservedAt: now.Add(-10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-r-old", "node-r-new", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control"],
|
|
"peer_endpoints": {
|
|
"node-r-old": "http://node-r-old:19000",
|
|
"node-r-new": "http://node-r-new:19000"
|
|
},
|
|
"peer_endpoint_candidates": {
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 5
|
|
}
|
|
]
|
|
},
|
|
"rendezvous_leases": [
|
|
{
|
|
"lease_id": "route-a-b-rv-node-b-via-node-r-old",
|
|
"peer_node_id": "node-b",
|
|
"relay_node_id": "node-r-old",
|
|
"relay_endpoint": "http://node-r-old:19000",
|
|
"priority": 4
|
|
}
|
|
]
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if len(cfg.RendezvousLeases) != 1 {
|
|
t.Fatalf("unexpected rendezvous leases: %+v", cfg.RendezvousLeases)
|
|
}
|
|
lease := cfg.RendezvousLeases[0]
|
|
if lease.RelayNodeID != "node-r-new" ||
|
|
lease.LeaseID != "route-a-b-rv-node-b-via-node-r-new" ||
|
|
lease.Reason != "stale_relay_replacement" {
|
|
t.Fatalf("stale relay was not replaced: %+v", lease)
|
|
}
|
|
var metadata map[string]any
|
|
if err := json.Unmarshal(lease.Metadata, &metadata); err != nil {
|
|
t.Fatalf("unmarshal lease metadata: %v", err)
|
|
}
|
|
if metadata["replacement_for_stale_relay"] != true ||
|
|
metadata["relay_replacement_contract"] != "stale_relay_feedback_policy" {
|
|
t.Fatalf("replacement metadata missing: %+v", metadata)
|
|
}
|
|
if cfg.RendezvousRelayPolicy == nil ||
|
|
cfg.RendezvousRelayPolicy.StaleRelayCount != 1 ||
|
|
cfg.RendezvousRelayPolicy.WithdrawnLeaseCount != 1 ||
|
|
cfg.RendezvousRelayPolicy.ReplacementLeaseCount != 1 {
|
|
t.Fatalf("unexpected relay policy report: %+v", cfg.RendezvousRelayPolicy)
|
|
}
|
|
var decision RendezvousRelayPolicyDecision
|
|
for _, item := range cfg.RendezvousRelayPolicy.Decisions {
|
|
if item.Reason == "stale_relay_replacement" {
|
|
decision = item
|
|
break
|
|
}
|
|
}
|
|
if decision.SelectedRelayID != "node-r-new" || decision.StaleRelayNodeID != "node-r-old" {
|
|
t.Fatalf("unexpected relay replacement decision: %+v", cfg.RendezvousRelayPolicy.Decisions)
|
|
}
|
|
if cfg.RoutePathDecisions == nil ||
|
|
cfg.RoutePathDecisions.SchemaVersion != "c17z18.route_path_decisions.v1" ||
|
|
cfg.RoutePathDecisions.DecisionCount != 1 ||
|
|
cfg.RoutePathDecisions.ReplacementDecisionCount != 1 {
|
|
t.Fatalf("unexpected route path decisions: %+v", cfg.RoutePathDecisions)
|
|
}
|
|
pathDecision := cfg.RoutePathDecisions.Decisions[0]
|
|
if pathDecision.DecisionSource != "stale_relay_replacement" ||
|
|
pathDecision.SelectedRelayID != "node-r-new" ||
|
|
pathDecision.StaleRelayNodeID != "node-r-old" ||
|
|
pathDecision.RendezvousPeerNodeID != "node-b" ||
|
|
pathDecision.RendezvousLeaseID != "route-a-b-rv-node-b-via-node-r-new" ||
|
|
pathDecision.NextHopID != "node-r-new" ||
|
|
pathDecision.ProductionForwarding ||
|
|
!pathDecision.ControlPlaneOnly ||
|
|
strings.Join(pathDecision.EffectiveHops, ",") != "node-a,node-r-new,node-b" {
|
|
t.Fatalf("unexpected route path decision: %+v", pathDecision)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigAppliesReplacementPathHintForExit(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 30, 0, 0, time.UTC)
|
|
hintMetadata, err := json.Marshal(map[string]any{
|
|
"mesh_route_path_decision_report": map[string]any{
|
|
"cluster_id": "cluster-1",
|
|
"node_id": "node-a",
|
|
"decisions": []map[string]any{
|
|
{
|
|
"decision_id": "route-a-b-path-node-a-via-node-r-new",
|
|
"route_id": "route-a-b",
|
|
"cluster_id": "cluster-1",
|
|
"local_node_id": "node-a",
|
|
"source_node_id": "node-a",
|
|
"destination_node_id": "node-b",
|
|
"original_hops": []string{"node-a", "node-r-old", "node-r-new", "node-b"},
|
|
"effective_hops": []string{"node-a", "node-r-new", "node-b"},
|
|
"next_hop_id": "node-r-new",
|
|
"local_role": "entry",
|
|
"selected_relay_id": "node-r-new",
|
|
"selected_relay_endpoint": "http://node-r-new:19000",
|
|
"stale_relay_node_id": "node-r-old",
|
|
"rendezvous_peer_node_id": "node-b",
|
|
"rendezvous_lease_id": "route-a-b-rv-node-b-via-node-r-new",
|
|
"rendezvous_lease_reason": "stale_relay_replacement",
|
|
"decision_source": "stale_relay_replacement",
|
|
"generation": "hint-generation",
|
|
"path_score": 900,
|
|
"score_reasons": []string{"route_path_decision_hint"},
|
|
"control_plane_only": true,
|
|
"production_forwarding": false,
|
|
"expires_at": now.Add(time.Hour).UTC().Format(time.RFC3339Nano),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("marshal hint metadata: %v", err)
|
|
}
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
heartbeats: map[string][]NodeHeartbeat{
|
|
"node-a": {
|
|
{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
Metadata: hintMetadata,
|
|
ObservedAt: now.Add(-10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-r-old", "node-r-new", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control"],
|
|
"peer_endpoints": {
|
|
"node-r-old": "http://node-r-old:19000",
|
|
"node-r-new": "http://node-r-new:19000"
|
|
},
|
|
"peer_endpoint_candidates": {
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 5
|
|
}
|
|
]
|
|
},
|
|
"rendezvous_leases": [
|
|
{
|
|
"lease_id": "route-a-b-rv-node-b-via-node-r-old",
|
|
"peer_node_id": "node-b",
|
|
"relay_node_id": "node-r-old",
|
|
"relay_endpoint": "http://node-r-old:19000",
|
|
"priority": 4
|
|
}
|
|
]
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-b",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if len(cfg.RendezvousLeases) != 1 ||
|
|
cfg.RendezvousLeases[0].RelayNodeID != "node-r-new" ||
|
|
cfg.RendezvousLeases[0].Reason != "stale_relay_replacement" {
|
|
t.Fatalf("replacement hint did not withdraw stale relay lease: %+v", cfg.RendezvousLeases)
|
|
}
|
|
if cfg.RoutePathDecisions == nil ||
|
|
cfg.RoutePathDecisions.ReplacementDecisionCount != 1 ||
|
|
len(cfg.RoutePathDecisions.Decisions) != 1 {
|
|
t.Fatalf("unexpected route path decisions: %+v", cfg.RoutePathDecisions)
|
|
}
|
|
decision := cfg.RoutePathDecisions.Decisions[0]
|
|
if decision.DecisionSource != "stale_relay_replacement" ||
|
|
decision.LocalRole != "exit" ||
|
|
decision.PreviousHopID != "node-r-new" ||
|
|
decision.SelectedRelayID != "node-r-new" ||
|
|
decision.StaleRelayNodeID != "node-r-old" ||
|
|
decision.RendezvousPeerNodeID != "node-b" ||
|
|
strings.Join(decision.EffectiveHops, ",") != "node-a,node-r-new,node-b" {
|
|
t.Fatalf("unexpected hinted route path decision: %+v", decision)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigUsesRouteHealthDriftToReselectRelay(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 30, 0, 0, time.UTC)
|
|
routeHealthMetadata, err := json.Marshal(map[string]any{
|
|
"observation_type": "synthetic_route_health",
|
|
"route_id": "route-a-b",
|
|
"route_path_decision_applied": true,
|
|
"route_path_decision_selected_relay_id": "node-s",
|
|
"route_path_decision_rendezvous_peer_node_id": "node-b",
|
|
"route_path_decision_rendezvous_lease_id": "route-a-b-rv-node-b-via-node-s",
|
|
"route_path_decision_rendezvous_lease_reason": "auto_rendezvous_required",
|
|
"expected_effective_hops": []string{"node-a", "node-s", "node-b"},
|
|
"observed_ack_path": []string{"node-a", "node-t", "node-b"},
|
|
"route_path_drift_detected": true,
|
|
"control_plane_only": true,
|
|
"production_forwarding": false,
|
|
"production_payload_forwarding": false,
|
|
"route_health_production_payload_forwarding": false,
|
|
"route_health_service_payload_forwarding": false,
|
|
"synthetic_route_health_route_path_runtime": true,
|
|
"production_route_path_forwarding_runtime": false,
|
|
"route_health_route_config_contract": "control_plane_route_path_decisions_to_synthetic_route_health",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("marshal route health metadata: %v", err)
|
|
}
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
meshLinks: []MeshLinkObservation{
|
|
{
|
|
ClusterID: "cluster-1",
|
|
SourceNodeID: "node-a",
|
|
TargetNodeID: "node-b",
|
|
LinkStatus: "reachable",
|
|
Metadata: routeHealthMetadata,
|
|
ObservedAt: now.Add(-10 * time.Second),
|
|
},
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-s", "node-t", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control"],
|
|
"peer_endpoint_candidates": {
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 5
|
|
}
|
|
],
|
|
"node-s": [
|
|
{
|
|
"endpoint_id": "node-s-public",
|
|
"node_id": "node-s",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "http://node-s:19000",
|
|
"reachability": "public",
|
|
"nat_type": "none",
|
|
"connectivity_mode": "direct",
|
|
"priority": 1,
|
|
"policy_tags": ["fast-path"]
|
|
}
|
|
],
|
|
"node-t": [
|
|
{
|
|
"endpoint_id": "node-t-public",
|
|
"node_id": "node-t",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "http://node-t:19000",
|
|
"reachability": "public",
|
|
"nat_type": "none",
|
|
"connectivity_mode": "direct",
|
|
"priority": 50
|
|
}
|
|
]
|
|
}
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if len(cfg.RendezvousLeases) != 1 {
|
|
t.Fatalf("unexpected rendezvous leases: %+v", cfg.RendezvousLeases)
|
|
}
|
|
lease := cfg.RendezvousLeases[0]
|
|
if lease.RelayNodeID != "node-t" || lease.Reason != "stale_relay_replacement" {
|
|
t.Fatalf("route health drift did not reselect relay: %+v", lease)
|
|
}
|
|
if cfg.RendezvousRelayPolicy == nil ||
|
|
cfg.RendezvousRelayPolicy.StaleRelayCount != 1 ||
|
|
cfg.RendezvousRelayPolicy.ReplacementLeaseCount != 1 ||
|
|
cfg.RendezvousRelayPolicy.ScoringMode != "route_adjacency_endpoint_priority_mesh_link_health_synthetic_route_health_feedback" {
|
|
t.Fatalf("unexpected relay policy report: %+v", cfg.RendezvousRelayPolicy)
|
|
}
|
|
var policyDecision RendezvousRelayPolicyDecision
|
|
for _, item := range cfg.RendezvousRelayPolicy.Decisions {
|
|
if item.Reason == "stale_relay_replacement" {
|
|
policyDecision = item
|
|
break
|
|
}
|
|
}
|
|
if policyDecision.StaleRelayNodeID != "node-s" || policyDecision.SelectedRelayID != "node-t" || policyDecision.PeerNodeID != "node-b" {
|
|
t.Fatalf("unexpected route health replacement decision: %+v", cfg.RendezvousRelayPolicy.Decisions)
|
|
}
|
|
if cfg.RoutePathDecisions == nil || cfg.RoutePathDecisions.ReplacementDecisionCount != 1 {
|
|
t.Fatalf("expected replacement route path decision: %+v", cfg.RoutePathDecisions)
|
|
}
|
|
decision := cfg.RoutePathDecisions.Decisions[0]
|
|
if decision.SelectedRelayID != "node-t" ||
|
|
decision.StaleRelayNodeID != "node-s" ||
|
|
decision.RendezvousPeerNodeID != "node-b" ||
|
|
strings.Join(decision.EffectiveHops, ",") != "node-a,node-t,node-b" ||
|
|
decision.ProductionForwarding ||
|
|
!decision.ControlPlaneOnly {
|
|
t.Fatalf("unexpected route path decision from route health feedback: %+v", decision)
|
|
}
|
|
}
|
|
|
|
func TestGetNodeSyntheticMeshConfigUsesRouteHealthLatencyForRelayScore(t *testing.T) {
|
|
now := time.Date(2026, 4, 28, 12, 30, 0, 0, time.UTC)
|
|
routeHealthMetadata, err := json.Marshal(map[string]any{
|
|
"observation_type": "synthetic_route_health",
|
|
"route_id": "route-a-b",
|
|
"route_path_decision_applied": true,
|
|
"route_path_decision_selected_relay_id": "node-t",
|
|
"route_path_decision_rendezvous_peer_node_id": "node-b",
|
|
"expected_effective_hops": []string{"node-a", "node-t", "node-b"},
|
|
"observed_ack_path": []string{"node-a", "node-t", "node-b"},
|
|
"route_path_drift_detected": false,
|
|
"control_plane_only": true,
|
|
"production_forwarding": false,
|
|
"production_payload_forwarding": false,
|
|
"route_health_production_payload_forwarding": false,
|
|
"route_health_service_payload_forwarding": false,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("marshal route health metadata: %v", err)
|
|
}
|
|
latency := 5
|
|
quality := 99
|
|
service := NewService(&fakeRepository{
|
|
testingFlags: EffectiveNodeTestingFlags{
|
|
Enabled: true,
|
|
SyntheticLinksEnabled: true,
|
|
},
|
|
meshLinks: []MeshLinkObservation{
|
|
{
|
|
ClusterID: "cluster-1",
|
|
SourceNodeID: "node-a",
|
|
TargetNodeID: "node-b",
|
|
LinkStatus: "reachable",
|
|
LatencyMs: &latency,
|
|
QualityScore: &quality,
|
|
Metadata: routeHealthMetadata,
|
|
ObservedAt: now.Add(-10 * time.Second),
|
|
},
|
|
},
|
|
routeIntents: []MeshRouteIntent{
|
|
{
|
|
ID: "route-a-b",
|
|
ClusterID: "cluster-1",
|
|
SourceSelector: json.RawMessage(`{"node_id":"node-a"}`),
|
|
DestinationSelector: json.RawMessage(`{"node_id":"node-b"}`),
|
|
ServiceClass: "synthetic",
|
|
Status: "active",
|
|
Policy: json.RawMessage(`{
|
|
"synthetic_enabled": true,
|
|
"hops": ["node-a", "node-s", "node-t", "node-b"],
|
|
"allowed_channels": ["fabric_control", "route_control"],
|
|
"peer_endpoint_candidates": {
|
|
"node-b": [
|
|
{
|
|
"endpoint_id": "node-b-outbound",
|
|
"node_id": "node-b",
|
|
"transport": "outbound_reverse",
|
|
"address": "node-b.reverse.local",
|
|
"reachability": "outbound_only",
|
|
"nat_type": "symmetric",
|
|
"connectivity_mode": "outbound_only",
|
|
"priority": 5
|
|
}
|
|
],
|
|
"node-s": [
|
|
{
|
|
"endpoint_id": "node-s-public",
|
|
"node_id": "node-s",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "http://node-s:19000",
|
|
"reachability": "public",
|
|
"nat_type": "none",
|
|
"connectivity_mode": "direct",
|
|
"priority": 1,
|
|
"policy_tags": ["fast-path"]
|
|
}
|
|
],
|
|
"node-t": [
|
|
{
|
|
"endpoint_id": "node-t-public",
|
|
"node_id": "node-t",
|
|
"transport": "direct_tcp_tls",
|
|
"address": "http://node-t:19000",
|
|
"reachability": "public",
|
|
"nat_type": "none",
|
|
"connectivity_mode": "direct",
|
|
"priority": 50
|
|
}
|
|
]
|
|
}
|
|
}`),
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
})
|
|
service.now = func() time.Time { return now }
|
|
|
|
cfg, err := service.GetNodeSyntheticMeshConfig(context.Background(), GetNodeSyntheticMeshConfigInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get synthetic config: %v", err)
|
|
}
|
|
if len(cfg.RendezvousLeases) != 1 {
|
|
t.Fatalf("unexpected rendezvous leases: %+v", cfg.RendezvousLeases)
|
|
}
|
|
lease := cfg.RendezvousLeases[0]
|
|
if lease.RelayNodeID != "node-t" || lease.Reason == "stale_relay_replacement" {
|
|
t.Fatalf("route health latency did not influence relay score: %+v", lease)
|
|
}
|
|
var metadata map[string]any
|
|
if err := json.Unmarshal(lease.Metadata, &metadata); err != nil {
|
|
t.Fatalf("unmarshal lease metadata: %v", err)
|
|
}
|
|
reasons, _ := metadata["relay_selection_score_reasons"].([]any)
|
|
if !anyString(reasons, "route_health_reachable") ||
|
|
!anyString(reasons, "route_health_no_drift") ||
|
|
!anyString(reasons, "route_health_latency") {
|
|
t.Fatalf("route health score reasons missing: %+v", metadata)
|
|
}
|
|
}
|
|
|
|
func anyString(values []any, want string) bool {
|
|
for _, value := range values {
|
|
if text, ok := value.(string); ok && text == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findPeerDirectoryEntry(entries []PeerDirectoryEntry, nodeID string) (PeerDirectoryEntry, bool) {
|
|
for _, entry := range entries {
|
|
if entry.NodeID == nodeID {
|
|
return entry, true
|
|
}
|
|
}
|
|
return PeerDirectoryEntry{}, false
|
|
}
|
|
|
|
func TestValidatePeerEndpointCandidates(t *testing.T) {
|
|
valid := map[string][]PeerEndpointCandidate{
|
|
"node-b": {
|
|
{
|
|
EndpointID: "node-b-public",
|
|
NodeID: "node-b",
|
|
Transport: "direct_tcp_tls",
|
|
Address: "203.0.113.20:443",
|
|
AddressFamily: "ipv4",
|
|
Reachability: "public",
|
|
NATType: "restricted",
|
|
ConnectivityMode: "direct",
|
|
Priority: 10,
|
|
Metadata: json.RawMessage(`{"source":"test"}`),
|
|
},
|
|
},
|
|
}
|
|
if err := validatePeerEndpointCandidates(valid, []string{"node-a", "node-b"}); err != nil {
|
|
t.Fatalf("validate valid candidates: %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
candidates map[string][]PeerEndpointCandidate
|
|
}{
|
|
{
|
|
name: "unknown transport",
|
|
candidates: map[string][]PeerEndpointCandidate{"node-b": {{
|
|
EndpointID: "node-b-public",
|
|
NodeID: "node-b",
|
|
Transport: "udp-hole-punch",
|
|
Address: "203.0.113.20:443",
|
|
Reachability: "public",
|
|
ConnectivityMode: "direct",
|
|
}}},
|
|
},
|
|
{
|
|
name: "unknown nat",
|
|
candidates: map[string][]PeerEndpointCandidate{"node-b": {{
|
|
EndpointID: "node-b-public",
|
|
NodeID: "node-b",
|
|
Transport: "direct_tcp_tls",
|
|
Address: "203.0.113.20:443",
|
|
Reachability: "public",
|
|
NATType: "mystery_nat",
|
|
ConnectivityMode: "direct",
|
|
}}},
|
|
},
|
|
{
|
|
name: "node outside route path",
|
|
candidates: map[string][]PeerEndpointCandidate{"node-y": {{
|
|
EndpointID: "node-y-public",
|
|
NodeID: "node-y",
|
|
Transport: "direct_tcp_tls",
|
|
Address: "203.0.113.30:443",
|
|
Reachability: "public",
|
|
ConnectivityMode: "direct",
|
|
}}},
|
|
},
|
|
{
|
|
name: "node mismatch",
|
|
candidates: map[string][]PeerEndpointCandidate{"node-b": {{
|
|
EndpointID: "node-b-public",
|
|
NodeID: "node-c",
|
|
Transport: "direct_tcp_tls",
|
|
Address: "203.0.113.20:443",
|
|
Reachability: "public",
|
|
ConnectivityMode: "direct",
|
|
}}},
|
|
},
|
|
{
|
|
name: "invalid metadata",
|
|
candidates: map[string][]PeerEndpointCandidate{"node-b": {{
|
|
EndpointID: "node-b-public",
|
|
NodeID: "node-b",
|
|
Transport: "direct_tcp_tls",
|
|
Address: "203.0.113.20:443",
|
|
Reachability: "public",
|
|
ConnectivityMode: "direct",
|
|
Metadata: json.RawMessage(`{`),
|
|
}}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validatePeerEndpointCandidates(tt.candidates, []string{"node-a", "node-b"})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMinorityClusterBlocksPolicyMutation(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
authorityState: ClusterAuthorityState{
|
|
ClusterID: "cluster-1",
|
|
AuthorityState: "minority",
|
|
MutationMode: "read_only",
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AssignNodeRole(context.Background(), AssignNodeRoleInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
Role: "rdp-worker",
|
|
})
|
|
if !errors.Is(err, ErrClusterReadOnly) {
|
|
t.Fatalf("err = %v, want ErrClusterReadOnly", err)
|
|
}
|
|
}
|
|
|
|
func TestRecoveryAdminCanMutateReadOnlyCluster(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleRecoveryAdmin,
|
|
authorityState: ClusterAuthorityState{
|
|
ClusterID: "cluster-1",
|
|
AuthorityState: "isolated",
|
|
MutationMode: "read_only",
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AssignNodeRole(context.Background(), AssignNodeRoleInput{
|
|
ActorUserID: "recovery-1",
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
Role: "rdp-worker",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("recovery admin mutate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateVPNConnectionRequiresPlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: "user"}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateVPNConnection(context.Background(), CreateVPNConnectionInput{
|
|
ActorUserID: "user-1",
|
|
ClusterID: "cluster-1",
|
|
OrganizationID: "org-1",
|
|
Name: "office-a",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateVPNConnectionDefaultsToDisabledSingleActive(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
item, err := service.CreateVPNConnection(context.Background(), CreateVPNConnectionInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
OrganizationID: "org-1",
|
|
Name: "office-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create vpn connection: %v", err)
|
|
}
|
|
if item.Mode != VPNConnectionModeSingleActive || item.DesiredState != VPNConnectionDesiredDisabled {
|
|
t.Fatalf("unexpected defaults: %+v", item)
|
|
}
|
|
if string(store.lastVPNConnectionInput.AllowedNodePolicy) == "" || string(store.lastVPNConnectionInput.RoutingUsage) == "" {
|
|
t.Fatalf("expected default json policies, got %+v", store.lastVPNConnectionInput)
|
|
}
|
|
}
|
|
|
|
func TestCreateVPNConnectionRequiresClusterAndOrganizationScope(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateVPNConnection(context.Background(), CreateVPNConnectionInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
Name: "office-a",
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateVPNConnectionBlockedInReadOnlyCluster(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
authorityState: ClusterAuthorityState{
|
|
ClusterID: "cluster-1",
|
|
AuthorityState: "minority",
|
|
MutationMode: "read_only",
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.CreateVPNConnection(context.Background(), CreateVPNConnectionInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
OrganizationID: "org-1",
|
|
Name: "office-a",
|
|
})
|
|
if !errors.Is(err, ErrClusterReadOnly) {
|
|
t.Fatalf("err = %v, want ErrClusterReadOnly", err)
|
|
}
|
|
}
|
|
|
|
func TestAcquireVPNLeaseRequiresEnabledConnection(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
vpnConnection: VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredDisabled,
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AcquireVPNConnectionLease(context.Background(), AcquireVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
OwnerNodeID: "node-1",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "enabled single_active") {
|
|
t.Fatalf("err = %v, want enabled single_active validation", err)
|
|
}
|
|
}
|
|
|
|
func TestAcquireVPNLeaseRejectsSecondActiveOwner(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
vpnConnection: VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredEnabled,
|
|
},
|
|
acquireVPNLeaseErr: ErrVPNLeaseAlreadyActive,
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AcquireVPNConnectionLease(context.Background(), AcquireVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
OwnerNodeID: "node-2",
|
|
})
|
|
if !errors.Is(err, ErrVPNLeaseAlreadyActive) {
|
|
t.Fatalf("err = %v, want ErrVPNLeaseAlreadyActive", err)
|
|
}
|
|
}
|
|
|
|
func TestAcquireVPNLeaseRejectsOwnerOutsideAllowedPolicy(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
vpnConnection: VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredEnabled,
|
|
},
|
|
ownerEligibility: VPNLeaseOwnerEligibility{
|
|
VPNConnectionID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
OwnerNodeID: "node-1",
|
|
MembershipStatus: "active",
|
|
NodeRegistrationStatus: NodeRegistrationActive,
|
|
AllowedByPolicy: false,
|
|
HasAuthorizedRole: true,
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AcquireVPNConnectionLease(context.Background(), AcquireVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
OwnerNodeID: "node-1",
|
|
})
|
|
if !errors.Is(err, ErrVPNLeaseOwnerNotAllowed) {
|
|
t.Fatalf("err = %v, want ErrVPNLeaseOwnerNotAllowed", err)
|
|
}
|
|
}
|
|
|
|
func TestAcquireVPNLeaseRejectsOwnerWithoutVPNRole(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
vpnConnection: VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredEnabled,
|
|
},
|
|
ownerEligibility: VPNLeaseOwnerEligibility{
|
|
VPNConnectionID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
OwnerNodeID: "node-1",
|
|
MembershipStatus: "active",
|
|
NodeRegistrationStatus: NodeRegistrationActive,
|
|
AllowedByPolicy: true,
|
|
HasAuthorizedRole: false,
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AcquireVPNConnectionLease(context.Background(), AcquireVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
OwnerNodeID: "node-1",
|
|
})
|
|
if !errors.Is(err, ErrVPNLeaseOwnerRoleRequired) {
|
|
t.Fatalf("err = %v, want ErrVPNLeaseOwnerRoleRequired", err)
|
|
}
|
|
}
|
|
|
|
func TestAcquireVPNLeaseRejectsWrongCluster(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
vpnConnection: VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredEnabled,
|
|
},
|
|
ownerEligibilityErr: pgx.ErrNoRows,
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.AcquireVPNConnectionLease(context.Background(), AcquireVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-other",
|
|
VPNConnectionID: "vpn-1",
|
|
OwnerNodeID: "node-1",
|
|
})
|
|
if !errors.Is(err, ErrInvalidVPNConnection) {
|
|
t.Fatalf("err = %v, want ErrInvalidVPNConnection", err)
|
|
}
|
|
}
|
|
|
|
func TestRenewVPNLeaseRejectsExpiredLease(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
ownerEligibility: VPNLeaseOwnerEligibility{
|
|
VPNConnectionID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
OwnerNodeID: "node-1",
|
|
MembershipStatus: "active",
|
|
NodeRegistrationStatus: NodeRegistrationActive,
|
|
AllowedByPolicy: true,
|
|
HasAuthorizedRole: true,
|
|
},
|
|
renewVPNLeaseErr: pgx.ErrNoRows,
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.RenewVPNConnectionLease(context.Background(), RenewVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
LeaseID: "lease-1",
|
|
OwnerNodeID: "node-1",
|
|
FencingToken: "token-1",
|
|
})
|
|
if !errors.Is(err, ErrInvalidVPNLease) {
|
|
t.Fatalf("err = %v, want ErrInvalidVPNLease", err)
|
|
}
|
|
}
|
|
|
|
func TestFenceVPNLeaseRequiresRecoveryAdmin(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.FenceVPNConnectionLease(context.Background(), FenceVPNConnectionLeaseInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
LeaseID: "lease-1",
|
|
})
|
|
if !errors.Is(err, ErrAccessDenied) {
|
|
t.Fatalf("err = %v, want ErrAccessDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestExpireStaleVPNConnectionLeasesAuditsEachExpiredLease(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: PlatformRoleAdmin,
|
|
expiredVPNLeases: []VPNConnectionLease{
|
|
{ID: "lease-1", ClusterID: "cluster-1", VPNConnectionID: "vpn-1", Status: VPNLeaseStatusExpired},
|
|
{ID: "lease-2", ClusterID: "cluster-1", VPNConnectionID: "vpn-2", Status: VPNLeaseStatusExpired},
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
items, err := service.ExpireStaleVPNConnectionLeases(context.Background(), ExpireStaleVPNConnectionLeasesInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expire stale vpn leases: %v", err)
|
|
}
|
|
if got, want := len(items), 2; got != want {
|
|
t.Fatalf("expired leases = %d, want %d", got, want)
|
|
}
|
|
var auditCount int
|
|
for _, event := range store.auditEvents {
|
|
if event.EventType == "vpn_connection.lease_expired" {
|
|
auditCount++
|
|
}
|
|
}
|
|
if got, want := auditCount, 2; got != want {
|
|
t.Fatalf("lease_expired audit count = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSetVPNConnectionAllowedNodesDeduplicatesScope(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
items, err := service.SetVPNConnectionAllowedNodes(context.Background(), SetVPNConnectionAllowedNodesInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
NodeIDs: []string{"node-1", "node-1", " ", "node-2"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("set allowed nodes: %v", err)
|
|
}
|
|
if got, want := len(store.lastAllowedNodesInput.NodeIDs), 2; got != want {
|
|
t.Fatalf("deduped nodes = %d, want %d", got, want)
|
|
}
|
|
if got, want := len(items), 2; got != want {
|
|
t.Fatalf("allowed nodes returned = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUpsertVPNRoutePolicyRejectsInvalidType(t *testing.T) {
|
|
store := &fakeRepository{platformRole: PlatformRoleAdmin}
|
|
service := NewService(store)
|
|
|
|
_, err := service.UpsertVPNConnectionRoutePolicy(context.Background(), UpsertVPNConnectionRoutePolicyInput{
|
|
ActorUserID: "admin-1",
|
|
ClusterID: "cluster-1",
|
|
VPNConnectionID: "vpn-1",
|
|
RouteType: "submarine",
|
|
Destination: "10.0.0.0/24",
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
}
|
|
|
|
func TestListNodeVPNAssignmentsDoesNotRequirePlatformAdmin(t *testing.T) {
|
|
store := &fakeRepository{
|
|
platformRole: "user",
|
|
nodeVPNAssignments: []NodeVPNAssignment{
|
|
{VPNConnectionID: "vpn-1", ClusterID: "cluster-1", OrganizationID: "org-1", AssignmentReason: "eligible_candidate"},
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
items, err := service.ListNodeVPNAssignments(context.Background(), "cluster-1", "node-1")
|
|
if err != nil {
|
|
t.Fatalf("list node vpn assignments: %v", err)
|
|
}
|
|
if got, want := len(items), 1; got != want {
|
|
t.Fatalf("assignments = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestReportNodeVPNAssignmentStatusRejectsInvisibleAssignment(t *testing.T) {
|
|
store := &fakeRepository{}
|
|
service := NewService(store)
|
|
|
|
_, err := service.ReportNodeVPNAssignmentStatus(context.Background(), ReportNodeVPNAssignmentStatusInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
VPNConnectionID: "vpn-foreign",
|
|
ObservedStatus: VPNAssignmentStatusAssigned,
|
|
})
|
|
if !errors.Is(err, ErrVPNLeaseOwnerNotAllowed) {
|
|
t.Fatalf("err = %v, want ErrVPNLeaseOwnerNotAllowed", err)
|
|
}
|
|
}
|
|
|
|
func TestReportNodeVPNAssignmentStatusAcceptsExplicitStates(t *testing.T) {
|
|
store := &fakeRepository{
|
|
nodeVPNAssignments: []NodeVPNAssignment{
|
|
{VPNConnectionID: "vpn-1", ClusterID: "cluster-1", OrganizationID: "org-1"},
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
status, err := service.ReportNodeVPNAssignmentStatus(context.Background(), ReportNodeVPNAssignmentStatusInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
VPNConnectionID: "vpn-1",
|
|
ObservedStatus: VPNAssignmentStatusLeaseRequired,
|
|
StatusPayload: json.RawMessage(`{"reason":"no_lease"}`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("report node vpn assignment status: %v", err)
|
|
}
|
|
if status.ObservedStatus != VPNAssignmentStatusLeaseRequired {
|
|
t.Fatalf("ObservedStatus = %q, want %q", status.ObservedStatus, VPNAssignmentStatusLeaseRequired)
|
|
}
|
|
}
|
|
|
|
func TestReportNodeVPNAssignmentStatusRejectsInvalidStatus(t *testing.T) {
|
|
store := &fakeRepository{
|
|
nodeVPNAssignments: []NodeVPNAssignment{
|
|
{VPNConnectionID: "vpn-1", ClusterID: "cluster-1", OrganizationID: "org-1"},
|
|
},
|
|
}
|
|
service := NewService(store)
|
|
|
|
_, err := service.ReportNodeVPNAssignmentStatus(context.Background(), ReportNodeVPNAssignmentStatusInput{
|
|
ClusterID: "cluster-1",
|
|
NodeID: "node-1",
|
|
VPNConnectionID: "vpn-1",
|
|
ObservedStatus: "running_tunnel",
|
|
})
|
|
if !errors.Is(err, ErrInvalidPayload) {
|
|
t.Fatalf("err = %v, want ErrInvalidPayload", err)
|
|
}
|
|
}
|
|
|
|
type fakeRepository struct {
|
|
platformRole string
|
|
lastTokenHash string
|
|
validTokenErr error
|
|
createJoinRequestID string
|
|
bootstrapJoinRequest NodeJoinRequest
|
|
clusterAuthority ClusterAuthorityKey
|
|
lastTokenAuthority json.RawMessage
|
|
lastApprovalAuthority json.RawMessage
|
|
authorityState ClusterAuthorityState
|
|
vpnConnection VPNConnection
|
|
lastVPNConnectionInput CreateVPNConnectionInput
|
|
lastAllowedNodesInput SetVPNConnectionAllowedNodesInput
|
|
lastAttachInput AttachExistingNodeInput
|
|
lastNodeGroupInput CreateNodeGroupInput
|
|
lastAssignGroupInput AssignNodeGroupInput
|
|
lastEntryPointInput CreateFabricEntryPointInput
|
|
lastEgressPoolInput CreateFabricEgressPoolInput
|
|
acquireVPNLeaseErr error
|
|
ownerEligibility VPNLeaseOwnerEligibility
|
|
ownerEligibilityErr error
|
|
renewVPNLeaseErr error
|
|
expiredVPNLeases []VPNConnectionLease
|
|
nodeVPNAssignments []NodeVPNAssignment
|
|
testingFlags EffectiveNodeTestingFlags
|
|
routeIntents []MeshRouteIntent
|
|
meshLinks []MeshLinkObservation
|
|
heartbeats map[string][]NodeHeartbeat
|
|
auditEvents []ClusterAuditEvent
|
|
}
|
|
|
|
func (f *fakeRepository) GetPlatformRole(context.Context, string) (string, error) {
|
|
return f.platformRole, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListClusters(context.Context) ([]Cluster, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetCluster(context.Context, string) (Cluster, error) {
|
|
return Cluster{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateCluster(context.Context, CreateClusterInput) (Cluster, error) {
|
|
return Cluster{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) UpdateCluster(_ context.Context, input UpdateClusterInput) (Cluster, error) {
|
|
return Cluster{
|
|
ID: input.ClusterID,
|
|
Slug: "cluster-1",
|
|
Name: input.Name,
|
|
Status: input.Status,
|
|
Region: input.Region,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetClusterAuthority(_ context.Context, clusterID string) (ClusterAuthorityKey, error) {
|
|
if f.clusterAuthority.PrivateKey == "" {
|
|
keys, err := clusterauth.GenerateKeyPair()
|
|
if err != nil {
|
|
return ClusterAuthorityKey{}, err
|
|
}
|
|
f.clusterAuthority = ClusterAuthorityKey{
|
|
ClusterAuthorityDescriptor: ClusterAuthorityDescriptor{
|
|
SchemaVersion: clusterauth.AuthoritySchemaVersion,
|
|
ClusterID: clusterID,
|
|
AuthorityState: "active",
|
|
KeyAlgorithm: clusterauth.AlgorithmEd25519,
|
|
PublicKey: keys.PublicKeyB64,
|
|
PublicKeyFingerprint: keys.Fingerprint,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
},
|
|
PrivateKey: keys.PrivateKeyB64,
|
|
}
|
|
}
|
|
if f.clusterAuthority.ClusterID == "" {
|
|
f.clusterAuthority.ClusterID = clusterID
|
|
}
|
|
return f.clusterAuthority, nil
|
|
}
|
|
|
|
func (f *fakeRepository) EnsureClusterAuthority(ctx context.Context, clusterID string, _ *string) (ClusterAuthorityKey, error) {
|
|
return f.GetClusterAuthority(ctx, clusterID)
|
|
}
|
|
|
|
func (f *fakeRepository) ListClusterNodes(context.Context, string) ([]ClusterNode, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListNodeGroups(context.Context, string) ([]ClusterNodeGroup, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateNodeGroup(_ context.Context, input CreateNodeGroupInput) (ClusterNodeGroup, error) {
|
|
f.lastNodeGroupInput = input
|
|
return ClusterNodeGroup{
|
|
ID: "group-1",
|
|
ClusterID: input.ClusterID,
|
|
ParentGroupID: input.ParentGroupID,
|
|
Name: input.Name,
|
|
Description: input.Description,
|
|
SortOrder: input.SortOrder,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) AssignNodeToGroup(_ context.Context, input AssignNodeGroupInput) (ClusterNode, error) {
|
|
f.lastAssignGroupInput = input
|
|
return ClusterNode{
|
|
ID: input.NodeID,
|
|
NodeKey: "node-key-1",
|
|
Name: "Node One",
|
|
RegistrationStatus: NodeRegistrationActive,
|
|
MembershipStatus: "active",
|
|
NodeGroupID: input.GroupID,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateJoinToken(_ context.Context, input CreateJoinTokenInput, tokenHash string) (NodeJoinToken, error) {
|
|
f.lastTokenHash = tokenHash
|
|
return NodeJoinToken{
|
|
ID: "token-1",
|
|
ClusterID: input.ClusterID,
|
|
Scope: input.Scope,
|
|
ExpiresAt: input.ExpiresAt,
|
|
MaxUses: input.MaxUses,
|
|
Status: "active",
|
|
CreatedByUserID: &input.ActorUserID,
|
|
CreatedAt: time.Now().UTC(),
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetJoinTokenAuthority(_ context.Context, clusterID, tokenID string, payload json.RawMessage, signature ClusterSignature) (NodeJoinToken, error) {
|
|
f.lastTokenAuthority = payload
|
|
return NodeJoinToken{
|
|
ID: tokenID,
|
|
ClusterID: clusterID,
|
|
Scope: json.RawMessage(`{"roles":["rdp-worker"]}`),
|
|
ExpiresAt: time.Now().UTC().Add(time.Hour),
|
|
MaxUses: 1,
|
|
Status: "active",
|
|
AuthorityPayload: payload,
|
|
AuthoritySignature: &signature,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetValidJoinTokenByHash(context.Context, string, string) (NodeJoinToken, error) {
|
|
if f.validTokenErr != nil {
|
|
return NodeJoinToken{}, f.validTokenErr
|
|
}
|
|
return NodeJoinToken{ID: "token-1", Status: "active", ExpiresAt: time.Now().Add(time.Hour), MaxUses: 1}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RevokeJoinToken(context.Context, RevokeJoinTokenInput) (NodeJoinToken, error) {
|
|
return NodeJoinToken{ID: "token-1", Status: "revoked"}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ExpireJoinTokens(context.Context, string) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateJoinRequest(_ context.Context, input CreateJoinRequestInput, joinTokenID string) (NodeJoinRequest, error) {
|
|
id := f.createJoinRequestID
|
|
if id == "" {
|
|
id = "join-request-1"
|
|
}
|
|
return NodeJoinRequest{
|
|
ID: id,
|
|
ClusterID: input.ClusterID,
|
|
JoinTokenID: &joinTokenID,
|
|
NodeName: input.NodeName,
|
|
NodeFingerprint: input.NodeFingerprint,
|
|
PublicKey: input.PublicKey,
|
|
ReportedCapabilities: input.ReportedCapabilities,
|
|
ReportedFacts: input.ReportedFacts,
|
|
RequestedRoles: input.RequestedRoles,
|
|
Status: JoinRequestStatusPending,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetJoinRequestForBootstrap(context.Context, GetJoinRequestBootstrapInput) (NodeJoinRequest, error) {
|
|
if f.bootstrapJoinRequest.ID != "" {
|
|
return f.bootstrapJoinRequest, nil
|
|
}
|
|
return NodeJoinRequest{ID: "join-request-1", ClusterID: "cluster-1", Status: JoinRequestStatusPending}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListJoinRequests(context.Context, string) ([]NodeJoinRequest, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ApproveJoinRequest(_ context.Context, input ApproveJoinRequestInput) (ApprovedJoinRequest, error) {
|
|
return ApprovedJoinRequest{
|
|
JoinRequest: NodeJoinRequest{ID: input.JoinRequestID, ClusterID: input.ClusterID, Status: JoinRequestStatusApproved, ApprovedNodeID: &input.NodeKey},
|
|
Bootstrap: NodeBootstrap{NodeID: input.NodeKey, ClusterID: input.ClusterID, IdentityStatus: "active"},
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetJoinRequestApprovalAuthority(_ context.Context, clusterID, joinRequestID string, payload json.RawMessage, signature ClusterSignature) (NodeJoinRequest, error) {
|
|
f.lastApprovalAuthority = payload
|
|
signatureRaw, _ := json.Marshal(signature)
|
|
nodeID := "node-1"
|
|
return NodeJoinRequest{
|
|
ID: joinRequestID,
|
|
ClusterID: clusterID,
|
|
Status: JoinRequestStatusApproved,
|
|
ApprovedNodeID: &nodeID,
|
|
ApprovalPayload: payload,
|
|
ApprovalSignature: signatureRaw,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RejectJoinRequest(context.Context, RejectJoinRequestInput) (NodeJoinRequest, error) {
|
|
return NodeJoinRequest{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) AssignNodeRole(_ context.Context, input AssignNodeRoleInput) (NodeRoleAssignment, error) {
|
|
return NodeRoleAssignment{ClusterID: input.ClusterID, NodeID: input.NodeID, Role: input.Role}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListNodeRoleAssignments(context.Context, string, string) ([]NodeRoleAssignment, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) AttachExistingNodeToCluster(_ context.Context, input AttachExistingNodeInput) (ClusterNode, error) {
|
|
f.lastAttachInput = input
|
|
return ClusterNode{
|
|
ID: input.NodeID,
|
|
NodeKey: "node-key-1",
|
|
Name: "Node One",
|
|
RegistrationStatus: NodeRegistrationActive,
|
|
MembershipStatus: "active",
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RecordHeartbeat(context.Context, RecordHeartbeatInput) (NodeHeartbeat, error) {
|
|
return NodeHeartbeat{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListNodeHeartbeats(_ context.Context, _ string, nodeID string, _ int) ([]NodeHeartbeat, error) {
|
|
return f.heartbeats[nodeID], nil
|
|
}
|
|
|
|
func (f *fakeRepository) RevokeNodeIdentity(context.Context, RevokeNodeIdentityInput) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRepository) DisableClusterMembership(context.Context, DisableMembershipInput) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRepository) UpsertFabricTestingFlag(_ context.Context, input UpsertFabricTestingFlagInput) (FabricTestingFlag, error) {
|
|
return FabricTestingFlag{
|
|
ScopeType: input.ScopeType,
|
|
ScopeID: input.ScopeID,
|
|
ClusterID: input.ClusterID,
|
|
Enabled: input.Enabled,
|
|
TelemetryEnabled: input.TelemetryEnabled,
|
|
SyntheticLinksEnabled: input.SyntheticLinksEnabled,
|
|
HistoryRetentionHours: input.HistoryRetentionHours,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListFabricTestingFlags(context.Context) ([]FabricTestingFlag, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetEffectiveNodeTestingFlags(context.Context, string, string) (EffectiveNodeTestingFlags, error) {
|
|
return f.testingFlags, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RecordNodeTelemetry(_ context.Context, input RecordNodeTelemetryInput) (NodeTelemetryObservation, error) {
|
|
return NodeTelemetryObservation{
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
Payload: input.Payload,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListNodeTelemetry(context.Context, string, string, int) ([]NodeTelemetryObservation, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetDesiredWorkload(_ context.Context, input SetDesiredWorkloadInput) (NodeWorkloadDesiredState, error) {
|
|
return NodeWorkloadDesiredState{
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
ServiceType: input.ServiceType,
|
|
DesiredState: input.DesiredState,
|
|
RuntimeMode: input.RuntimeMode,
|
|
Config: input.Config,
|
|
Environment: input.Environment,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListDesiredWorkloads(context.Context, string, string) ([]NodeWorkloadDesiredState, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ReportWorkloadStatus(_ context.Context, input ReportWorkloadStatusInput) (NodeWorkloadStatus, error) {
|
|
return NodeWorkloadStatus{
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
ServiceType: input.ServiceType,
|
|
ReportedState: input.ReportedState,
|
|
RuntimeMode: input.RuntimeMode,
|
|
StatusPayload: input.StatusPayload,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListLatestWorkloadStatuses(context.Context, string, string) ([]NodeWorkloadStatus, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ReportMeshLink(_ context.Context, input ReportMeshLinkInput) (MeshLinkObservation, error) {
|
|
return MeshLinkObservation{
|
|
ClusterID: input.ClusterID,
|
|
SourceNodeID: input.SourceNodeID,
|
|
TargetNodeID: input.TargetNodeID,
|
|
LinkStatus: input.LinkStatus,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListMeshLinks(context.Context, string) ([]MeshLinkObservation, error) {
|
|
return f.meshLinks, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateRouteIntent(_ context.Context, input CreateRouteIntentInput) (MeshRouteIntent, error) {
|
|
return MeshRouteIntent{
|
|
ClusterID: input.ClusterID,
|
|
SourceSelector: input.SourceSelector,
|
|
DestinationSelector: input.DestinationSelector,
|
|
ServiceClass: input.ServiceClass,
|
|
Priority: input.Priority,
|
|
Policy: input.Policy,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListRouteIntents(context.Context, string) ([]MeshRouteIntent, error) {
|
|
return f.routeIntents, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListQoSPolicies(context.Context, string) ([]MeshQoSPolicy, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListFabricEntryPoints(context.Context, string) ([]FabricEntryPoint, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateFabricEntryPoint(_ context.Context, input CreateFabricEntryPointInput) (FabricEntryPoint, error) {
|
|
f.lastEntryPointInput = input
|
|
return FabricEntryPoint{
|
|
ID: "entry-1",
|
|
ClusterID: input.ClusterID,
|
|
Name: input.Name,
|
|
Status: input.Status,
|
|
EndpointType: input.EndpointType,
|
|
PublicEndpoint: input.PublicEndpoint,
|
|
Policy: input.Policy,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetFabricEntryPointNode(_ context.Context, input SetFabricEntryPointNodeInput) (FabricEntryPointNode, error) {
|
|
return FabricEntryPointNode{
|
|
EntryPointID: input.EntryPointID,
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
Status: input.Status,
|
|
Priority: input.Priority,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListFabricEntryPointNodes(context.Context, string, string) ([]FabricEntryPointNode, error) {
|
|
return []FabricEntryPointNode{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListFabricEgressPools(context.Context, string) ([]FabricEgressPool, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateFabricEgressPool(_ context.Context, input CreateFabricEgressPoolInput) (FabricEgressPool, error) {
|
|
f.lastEgressPoolInput = input
|
|
return FabricEgressPool{
|
|
ID: "egress-1",
|
|
ClusterID: input.ClusterID,
|
|
Name: input.Name,
|
|
Status: input.Status,
|
|
Description: input.Description,
|
|
RouteScope: input.RouteScope,
|
|
Policy: input.Policy,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetFabricEgressPoolNode(_ context.Context, input SetFabricEgressPoolNodeInput) (FabricEgressPoolNode, error) {
|
|
return FabricEgressPoolNode{
|
|
EgressPoolID: input.EgressPoolID,
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
Status: input.Status,
|
|
Priority: input.Priority,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListFabricEgressPoolNodes(context.Context, string, string) ([]FabricEgressPoolNode, error) {
|
|
return []FabricEgressPoolNode{}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetClusterAuthorityState(context.Context, string) (ClusterAuthorityState, error) {
|
|
if f.authorityState.ClusterID == "" {
|
|
return ClusterAuthorityState{ClusterID: "cluster-1", AuthorityState: "authoritative", MutationMode: "normal"}, nil
|
|
}
|
|
return f.authorityState, nil
|
|
}
|
|
|
|
func (f *fakeRepository) UpdateClusterAuthorityState(_ context.Context, input UpdateClusterAuthorityInput) (ClusterAuthorityState, error) {
|
|
return ClusterAuthorityState{
|
|
ClusterID: input.ClusterID,
|
|
AuthorityState: input.AuthorityState,
|
|
MutationMode: input.MutationMode,
|
|
Notes: input.Notes,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListClusterAdminSummaries(context.Context) ([]ClusterAdminSummary, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CreateVPNConnection(_ context.Context, input CreateVPNConnectionInput) (VPNConnection, error) {
|
|
f.lastVPNConnectionInput = input
|
|
return VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: input.ClusterID,
|
|
OrganizationID: input.OrganizationID,
|
|
Name: input.Name,
|
|
TargetEndpoint: input.TargetEndpoint,
|
|
ProtocolFamily: input.ProtocolFamily,
|
|
CredentialRef: input.CredentialRef,
|
|
Mode: input.Mode,
|
|
DesiredState: input.DesiredState,
|
|
AllowedNodePolicy: input.AllowedNodePolicy,
|
|
RoutingUsage: input.RoutingUsage,
|
|
RoutePolicy: input.RoutePolicy,
|
|
QoSPolicy: input.QoSPolicy,
|
|
PlacementPolicy: input.PlacementPolicy,
|
|
Status: VPNConnectionStatusDisabled,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListVPNConnections(context.Context, string) ([]VPNConnection, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetVPNConnection(context.Context, string, string) (VPNConnection, error) {
|
|
if f.vpnConnection.ID != "" {
|
|
return f.vpnConnection, nil
|
|
}
|
|
return VPNConnection{
|
|
ID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
Mode: VPNConnectionModeSingleActive,
|
|
DesiredState: VPNConnectionDesiredEnabled,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) UpdateVPNConnectionDesiredState(_ context.Context, input UpdateVPNConnectionDesiredStateInput) (VPNConnection, error) {
|
|
return VPNConnection{ID: input.VPNConnectionID, ClusterID: input.ClusterID, DesiredState: input.DesiredState}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) UpsertVPNConnectionRoutePolicy(_ context.Context, input UpsertVPNConnectionRoutePolicyInput) (VPNConnectionRoutePolicy, error) {
|
|
return VPNConnectionRoutePolicy{
|
|
ID: "route-policy-1",
|
|
VPNConnectionID: input.VPNConnectionID,
|
|
ClusterID: input.ClusterID,
|
|
RouteType: input.RouteType,
|
|
Destination: input.Destination,
|
|
Action: input.Action,
|
|
ServiceType: input.ServiceType,
|
|
Priority: input.Priority,
|
|
Policy: input.Policy,
|
|
Status: input.Status,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListVPNConnectionRoutePolicies(context.Context, string, string) ([]VPNConnectionRoutePolicy, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) SetVPNConnectionAllowedNodes(_ context.Context, input SetVPNConnectionAllowedNodesInput) ([]VPNConnectionAllowedNode, error) {
|
|
f.lastAllowedNodesInput = input
|
|
items := make([]VPNConnectionAllowedNode, 0, len(input.NodeIDs))
|
|
for _, nodeID := range input.NodeIDs {
|
|
items = append(items, VPNConnectionAllowedNode{
|
|
VPNConnectionID: input.VPNConnectionID,
|
|
ClusterID: input.ClusterID,
|
|
NodeID: nodeID,
|
|
RolePreference: input.RolePreference,
|
|
Status: "active",
|
|
Metadata: input.Metadata,
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListVPNConnectionAllowedNodes(context.Context, string, string) ([]VPNConnectionAllowedNode, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeRepository) AcquireVPNConnectionLease(_ context.Context, input AcquireVPNConnectionLeaseInput, expiresAt time.Time, fencingToken string) (VPNConnectionLease, error) {
|
|
if f.acquireVPNLeaseErr != nil {
|
|
return VPNConnectionLease{}, f.acquireVPNLeaseErr
|
|
}
|
|
return VPNConnectionLease{
|
|
ID: "lease-1",
|
|
VPNConnectionID: input.VPNConnectionID,
|
|
ClusterID: input.ClusterID,
|
|
OwnerNodeID: input.OwnerNodeID,
|
|
LeaseGeneration: 1,
|
|
FencingToken: fencingToken,
|
|
Status: VPNLeaseStatusActive,
|
|
ExpiresAt: expiresAt,
|
|
Metadata: input.Metadata,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RenewVPNConnectionLease(_ context.Context, input RenewVPNConnectionLeaseInput, expiresAt time.Time) (VPNConnectionLease, error) {
|
|
if f.renewVPNLeaseErr != nil {
|
|
return VPNConnectionLease{}, f.renewVPNLeaseErr
|
|
}
|
|
return VPNConnectionLease{ID: input.LeaseID, VPNConnectionID: input.VPNConnectionID, ClusterID: input.ClusterID, OwnerNodeID: input.OwnerNodeID, FencingToken: input.FencingToken, Status: VPNLeaseStatusActive, ExpiresAt: expiresAt}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ReleaseVPNConnectionLease(_ context.Context, input ReleaseVPNConnectionLeaseInput) (VPNConnectionLease, error) {
|
|
return VPNConnectionLease{ID: input.LeaseID, VPNConnectionID: input.VPNConnectionID, ClusterID: input.ClusterID, OwnerNodeID: input.OwnerNodeID, FencingToken: input.FencingToken, Status: VPNLeaseStatusReleased}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) FenceVPNConnectionLease(_ context.Context, input FenceVPNConnectionLeaseInput) (VPNConnectionLease, error) {
|
|
return VPNConnectionLease{ID: input.LeaseID, VPNConnectionID: input.VPNConnectionID, ClusterID: input.ClusterID, Status: VPNLeaseStatusFenced}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) GetActiveVPNConnectionLease(context.Context, string, string) (VPNConnectionLease, error) {
|
|
return VPNConnectionLease{ID: "lease-1", Status: VPNLeaseStatusActive}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) CheckVPNLeaseOwnerEligibility(context.Context, string, string, string) (VPNLeaseOwnerEligibility, error) {
|
|
if f.ownerEligibilityErr != nil {
|
|
return VPNLeaseOwnerEligibility{}, f.ownerEligibilityErr
|
|
}
|
|
if f.ownerEligibility.VPNConnectionID != "" {
|
|
return f.ownerEligibility, nil
|
|
}
|
|
return VPNLeaseOwnerEligibility{
|
|
VPNConnectionID: "vpn-1",
|
|
ClusterID: "cluster-1",
|
|
OrganizationID: "org-1",
|
|
OwnerNodeID: "node-1",
|
|
MembershipStatus: "active",
|
|
NodeRegistrationStatus: NodeRegistrationActive,
|
|
AllowedByPolicy: true,
|
|
HasAuthorizedRole: true,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ExpireStaleVPNConnectionLeases(context.Context, string, time.Time) ([]VPNConnectionLease, error) {
|
|
return f.expiredVPNLeases, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListNodeVPNAssignments(context.Context, string, string) ([]NodeVPNAssignment, error) {
|
|
return f.nodeVPNAssignments, nil
|
|
}
|
|
|
|
func (f *fakeRepository) ReportNodeVPNAssignmentStatus(_ context.Context, input ReportNodeVPNAssignmentStatusInput) (NodeVPNAssignmentStatus, error) {
|
|
return NodeVPNAssignmentStatus{
|
|
ID: "status-1",
|
|
VPNConnectionID: input.VPNConnectionID,
|
|
ClusterID: input.ClusterID,
|
|
NodeID: input.NodeID,
|
|
ObservedStatus: input.ObservedStatus,
|
|
StatusPayload: input.StatusPayload,
|
|
ObservedAt: input.ObservedAt,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRepository) RecordAudit(_ context.Context, event ClusterAuditEvent) error {
|
|
f.auditEvents = append(f.auditEvents, event)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRepository) ListAuditEvents(context.Context, string, int) ([]ClusterAuditEvent, error) {
|
|
return nil, nil
|
|
}
|