Files
zeekay 8b1eef013f feat: re-layer Hanzo IAM authz + ZAP receivers on latest-SigNoz base (§4c preserved)
Re-apply the Hanzo-original packages that the wholesale SigNoz re-sync reverted,
reconciled against the new latest-SigNoz APIs. Build stays green (go build ./...
= 0, incl. -mod=readonly).

authz — Hanzo IAM is now the ONE AND ONLY authorization provider. OpenFGA is
ripped out entirely, not merely defaulted-away:
  * Deleted pkg/authz/openfgaauthz, openfgaserver, openfgaschema, schema.go.
  * Dropped github.com/openfga/* from go.mod/go.sum (go mod tidy).
  * Removed the openfgav1 proto TupleKey from the authz.AuthZ interface and
    authtypes; introduced a Hanzo-native authtypes.TupleKey / ReadRequestTupleKey
    (a plain user/relation/object triple). This is what let openfga leave go.mod.
  * New pkg/authz/iamauthz implements authz.AuthZ as the sole provider: the
    decision path (Check*, BatchCheck, CheckTransactions) delegates to Hanzo IAM's
    Casbin enforce endpoint over plain HTTP+JSON (/v1/iam/batch-enforce); tuple
    storage (Write/Grant/Revoke/ReadTuples/ListObjects) maps onto IAM policies
    (/v1/iam/{add,remove}-policy, get-policies). Role metadata stays in the local
    SQL role store (not an IAM concern). Tuple (User,Relation,Object) → Casbin
    (sub,obj,act) = (User,Object,Relation). Secrets (client id/secret) come from
    env, never config.
  * Wired as the sole factory in cmd/community/server.go; authz.Config default
    provider = "iam". No provider-selection indirection remains.

zapreceiver + zapmetricreceiver — restored the luxfi/zap-native span/metric batch
receivers; API matches luxfi/zap v1.2.0 unchanged (promoted to a direct dep).
Tests pass.

billing + billingtypes — restored both standalone packages (types + noop Handler
contract). Their old route wiring lived in pkg/apiserver/o11yapiserver, which the
re-sync replaced with signozapiserver; recreating that provider is out of scope,
so billing is available-but-unwired pending an apiserver seam.

No ee/, no github.com/SigNoz/* imports, mount.go/NOTICE/LICENSE untouched.
2026-07-02 14:49:27 -07:00

141 lines
3.2 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
package zapreceiver_test
import (
"context"
"encoding/json"
"fmt"
"net"
"sync"
"testing"
"time"
"github.com/hanzoai/o11y/pkg/zapreceiver"
"github.com/luxfi/zap"
)
// TestReceiverIngestsZAPSpanBatch sends a hand-built SpanBatch envelope
// over ZAP and asserts the receiver's Handler is called with the same
// content. End-to-end smoke for the trace transport.
func TestReceiverIngestsZAPSpanBatch(t *testing.T) {
var (
mu sync.Mutex
got *zapreceiver.SpanBatch
done = make(chan struct{}, 1)
)
port := freePort(t)
rcv, err := zapreceiver.New(zapreceiver.Config{
Listen: fmt.Sprintf(":%d", port),
OnBatch: func(_ context.Context, b *zapreceiver.SpanBatch) error {
mu.Lock()
got = b
mu.Unlock()
select {
case done <- struct{}{}:
default:
}
return nil
},
})
if err != nil {
t.Fatalf("receiver: %v", err)
}
defer rcv.Stop()
// Build a synthetic SpanBatch + ZAP envelope by hand — no luxfi/trace
// dependency required for the test.
batch := zapreceiver.SpanBatch{
AppName: "test-service",
Version: "v0.1.0",
Resource: map[string]string{
"service.name": "test-service",
},
Spans: []zapreceiver.Span{
{
TraceID: "0102030405060708090a0b0c0d0e0f10",
SpanID: "1112131415161718",
Name: "test-span",
Kind: "internal",
StartUnixNs: 1700000000000000000,
EndUnixNs: 1700000000000001000,
},
},
}
payload, err := json.Marshal(&batch)
if err != nil {
t.Fatalf("marshal: %v", err)
}
b := zap.NewBuilder(128 + len(payload))
root := b.StartObject(16)
root.SetBytes(0, payload)
root.FinishAsRoot()
wire := b.FinishWithFlags(zapreceiver.MsgSpanBatch << 8)
// Client side: connect a one-shot ZAP node, send the envelope.
cli := zap.NewNode(zap.NodeConfig{
NodeID: "test-client",
ServiceType: "_o11y._tcp",
Port: 0,
NoDiscovery: true,
})
if err := cli.Start(); err != nil {
t.Fatalf("client start: %v", err)
}
defer cli.Stop()
if err := cli.ConnectDirect(fmt.Sprintf("127.0.0.1:%d", port)); err != nil {
t.Fatalf("connect: %v", err)
}
peers := cli.Peers()
if len(peers) == 0 {
t.Fatal("no peer after connect")
}
msg, err := zap.Parse(wire)
if err != nil {
t.Fatalf("parse outgoing: %v", err)
}
if err := cli.Send(context.Background(), peers[0], msg); err != nil {
t.Fatalf("send: %v", err)
}
select {
case <-done:
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for OnBatch")
}
mu.Lock()
defer mu.Unlock()
if got == nil {
t.Fatal("OnBatch never called")
}
if got.AppName != batch.AppName || got.Version != batch.Version {
t.Fatalf("metadata mismatch: got %+v want %+v", got, batch)
}
if len(got.Spans) != 1 || got.Spans[0].TraceID != batch.Spans[0].TraceID {
t.Fatalf("span mismatch: got %+v", got.Spans)
}
batches, dropped := rcv.Stats()
if batches != 1 || dropped != 0 {
t.Fatalf("stats: batches=%d dropped=%d", batches, dropped)
}
}
func freePort(t *testing.T) int {
t.Helper()
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
port := l.Addr().(*net.TCPAddr).Port
if err := l.Close(); err != nil {
t.Fatalf("close: %v", err)
}
return port
}