Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 136d0843e4 |
@@ -19,6 +19,9 @@ import (
|
||||
"github.com/coder/coder/v2/agent/boundarylogproxy/codec"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/agentapi"
|
||||
"github.com/coder/coder/v2/coderd/boundaryusage"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
@@ -64,27 +67,37 @@ func sendBoundaryLogsRequest(t *testing.T, conn net.Conn, req *agentproto.Report
|
||||
|
||||
// TestBoundaryLogs_EndToEnd is an end-to-end test that sends a protobuf
|
||||
// message over the agent's unix socket (as boundary would) and verifies
|
||||
// it is ultimately logged by coderd with the correct structured fields.
|
||||
// it is ultimately logged by coderd with the correct structured fields
|
||||
// and that usage statistics are tracked properly.
|
||||
func TestBoundaryLogs_EndToEnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
socketPath := filepath.Join(testutil.TempDirUnixSocket(t), "boundary.sock")
|
||||
srv := boundarylogproxy.NewServer(testutil.Logger(t), socketPath)
|
||||
|
||||
err := srv.Start()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, srv.Close()) })
|
||||
const maxStalenessMs = 60000
|
||||
|
||||
sink := &logSink{}
|
||||
logger := slog.Make(sink)
|
||||
workspaceID := uuid.New()
|
||||
ownerID := uuid.New()
|
||||
templateID := uuid.New()
|
||||
templateVersionID := uuid.New()
|
||||
replicaID := uuid.New()
|
||||
tracker := boundaryusage.NewTracker()
|
||||
|
||||
reporter := &agentapi.BoundaryLogsAPI{
|
||||
Log: logger,
|
||||
WorkspaceID: workspaceID,
|
||||
TemplateID: templateID,
|
||||
TemplateVersionID: templateVersionID,
|
||||
Log: logger,
|
||||
WorkspaceID: workspaceID,
|
||||
OwnerID: ownerID,
|
||||
TemplateID: templateID,
|
||||
TemplateVersionID: templateVersionID,
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -169,4 +182,19 @@ func TestBoundaryLogs_EndToEnd(t *testing.T) {
|
||||
|
||||
cancel()
|
||||
<-forwarderDone
|
||||
|
||||
// Verify usage tracking: flush tracker to database and check counts.
|
||||
// Use a fresh context since the forwarder context has been canceled.
|
||||
dbCtx := testutil.Context(t, testutil.WaitShort)
|
||||
err = tracker.FlushToDB(dbCtx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(dbCtx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, maxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.UniqueUsers)
|
||||
require.Equal(t, int64(1), summary.AllowedRequests)
|
||||
require.Equal(t, int64(1), summary.DeniedRequests)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package agentapi_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"cdr.dev/slog/v3/sloggers/slogtest"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/agentapi"
|
||||
"github.com/coder/coder/v2/coderd/boundaryusage"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
const testMaxStalenessMs = 60000
|
||||
|
||||
func TestBoundaryLogsAPI_ReportBoundaryLogs_MultipleBatches(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
tracker := boundaryusage.NewTracker()
|
||||
workspaceID := uuid.New()
|
||||
ownerID := uuid.New()
|
||||
replicaID := uuid.New()
|
||||
|
||||
api := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
WorkspaceID: workspaceID,
|
||||
OwnerID: ownerID,
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
// First batch: 3 allowed, 1 denied.
|
||||
req1 := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://a.com"}}},
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://b.com"}}},
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://c.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked.com"}}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := api.ReportBoundaryLogs(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Second batch: 1 allowed, 2 denied.
|
||||
req2 := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://a.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked1.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked2.com"}}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = api.ReportBoundaryLogs(ctx, req2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tracker.FlushToDB(ctx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.UniqueUsers)
|
||||
require.Equal(t, int64(3+1), summary.AllowedRequests)
|
||||
require.Equal(t, int64(1+2), summary.DeniedRequests)
|
||||
}
|
||||
|
||||
func TestBoundaryLogsAPI_ReportBoundaryLogs_EmptyRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
tracker := boundaryusage.NewTracker()
|
||||
replicaID := uuid.New()
|
||||
|
||||
api := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
WorkspaceID: uuid.New(),
|
||||
OwnerID: uuid.New(),
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
// Send an empty request with no logs, then flush and verify no usage was
|
||||
// tracked.
|
||||
req := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{},
|
||||
}
|
||||
|
||||
_, err := api.ReportBoundaryLogs(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tracker.FlushToDB(ctx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(0), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(0), summary.UniqueUsers)
|
||||
require.Equal(t, int64(0), summary.AllowedRequests)
|
||||
require.Equal(t, int64(0), summary.DeniedRequests)
|
||||
}
|
||||
|
||||
func TestBoundaryLogsAPI_ReportBoundaryLogs_InvalidLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
tracker := boundaryusage.NewTracker()
|
||||
replicaID := uuid.New()
|
||||
|
||||
api := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
WorkspaceID: uuid.New(),
|
||||
OwnerID: uuid.New(),
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
req := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{
|
||||
Allowed: true,
|
||||
Time: timestamppb.Now(),
|
||||
Resource: &agentproto.BoundaryLog_HttpRequest_{
|
||||
HttpRequest: nil, // Invalid: nil HTTP request
|
||||
},
|
||||
},
|
||||
{
|
||||
Allowed: true,
|
||||
Time: timestamppb.Now(),
|
||||
Resource: &agentproto.BoundaryLog_HttpRequest_{
|
||||
HttpRequest: &agentproto.BoundaryLog_HttpRequest{
|
||||
Method: "GET",
|
||||
Url: "https://valid.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := api.ReportBoundaryLogs(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Flush and verify only the valid log was tracked.
|
||||
err = tracker.FlushToDB(ctx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.UniqueUsers)
|
||||
require.Equal(t, int64(1), summary.AllowedRequests)
|
||||
require.Equal(t, int64(0), summary.DeniedRequests)
|
||||
}
|
||||
|
||||
func TestBoundaryLogsAPI_ReportBoundaryLogs_MultipleWorkspaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
tracker := boundaryusage.NewTracker()
|
||||
replicaID := uuid.New()
|
||||
|
||||
// Simulate multiple workspaces reporting through different API instances.
|
||||
workspace1, workspace2 := uuid.New(), uuid.New()
|
||||
owner1, owner2 := uuid.New(), uuid.New()
|
||||
|
||||
api1 := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
WorkspaceID: workspace1,
|
||||
OwnerID: owner1,
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
api2 := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
WorkspaceID: workspace2,
|
||||
OwnerID: owner2,
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
req := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://example.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked.com"}}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := api1.ReportBoundaryLogs(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = api2.ReportBoundaryLogs(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Flush and verify both workspaces are tracked.
|
||||
err = tracker.FlushToDB(ctx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(2), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(2), summary.UniqueUsers)
|
||||
require.Equal(t, int64(1+1), summary.AllowedRequests)
|
||||
require.Equal(t, int64(1+1), summary.DeniedRequests)
|
||||
}
|
||||
@@ -16,6 +16,10 @@ import (
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// testMaxStalenessMs is the maximum staleness in milliseconds for boundary
|
||||
// usage stats queries in tests.
|
||||
const testMaxStalenessMs = 60000
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m, testutil.GoleakOptions...)
|
||||
}
|
||||
@@ -45,7 +49,7 @@ func TestTracker_Track_Single(t *testing.T) {
|
||||
|
||||
// Verify the data was written correctly.
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.UniqueUsers)
|
||||
@@ -73,7 +77,7 @@ func TestTracker_Track_DuplicateWorkspaceUser(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces, "should be 1 unique workspace")
|
||||
require.Equal(t, int64(1), summary.UniqueUsers, "should be 1 unique user")
|
||||
@@ -102,7 +106,7 @@ func TestTracker_Track_MultipleWorkspacesUsers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(3), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(2), summary.UniqueUsers)
|
||||
@@ -140,7 +144,7 @@ func TestTracker_Track_Concurrent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(numGoroutines), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(numGoroutines), summary.UniqueUsers)
|
||||
@@ -180,7 +184,7 @@ func TestTracker_FlushToDB_Accumulates(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.UniqueUsers)
|
||||
@@ -220,7 +224,7 @@ func TestTracker_FlushToDB_NewPeriod(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// The summary should only contain the new data after reset.
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces, "should only count new workspace")
|
||||
require.Equal(t, int64(1), summary.UniqueUsers, "should only count new user")
|
||||
@@ -242,7 +246,7 @@ func TestTracker_FlushToDB_NoActivity(t *testing.T) {
|
||||
|
||||
// Verify nothing was written to DB.
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(0), summary.AllowedRequests)
|
||||
@@ -297,7 +301,7 @@ func TestUpsertBoundaryUsageStats_Update(t *testing.T) {
|
||||
require.False(t, newPeriod, "should return false for update")
|
||||
|
||||
// Verify the update took effect.
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(8), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(5), summary.UniqueUsers)
|
||||
@@ -343,7 +347,7 @@ func TestGetBoundaryUsageSummary_MultipleReplicas(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify aggregation (SUM of all replicas).
|
||||
@@ -359,7 +363,7 @@ func TestGetBoundaryUsageSummary_Empty(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := dbauthz.AsBoundaryUsageTracker(context.Background())
|
||||
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// COALESCE should return 0 for all columns.
|
||||
@@ -388,7 +392,7 @@ func TestResetBoundaryUsageStats(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify data exists.
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, summary.AllowedRequests, int64(0))
|
||||
|
||||
@@ -397,7 +401,7 @@ func TestResetBoundaryUsageStats(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify all data is gone.
|
||||
summary, err = db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err = db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(0), summary.AllowedRequests)
|
||||
@@ -436,7 +440,7 @@ func TestDeleteBoundaryUsageStatsByReplicaID(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify only replica2's stats remain.
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(ctx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(20), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(200), summary.AllowedRequests)
|
||||
@@ -474,7 +478,7 @@ func TestTracker_TelemetryCycle(t *testing.T) {
|
||||
require.NoError(t, tracker3.FlushToDB(ctx, db, replica3))
|
||||
|
||||
// Telemetry aggregates.
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify aggregation.
|
||||
@@ -491,7 +495,7 @@ func TestTracker_TelemetryCycle(t *testing.T) {
|
||||
require.NoError(t, tracker1.FlushToDB(ctx, db, replica1))
|
||||
|
||||
// Verify trackers reset their in-memory state.
|
||||
summary, err = db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err = db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), summary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(1), summary.AllowedRequests)
|
||||
@@ -535,7 +539,7 @@ func TestTracker_ConcurrentFlushAndTrack(t *testing.T) {
|
||||
|
||||
// Verify stats are non-negative.
|
||||
boundaryCtx := dbauthz.AsBoundaryUsageTracker(ctx)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, 60000)
|
||||
summary, err := db.GetBoundaryUsageSummary(boundaryCtx, testMaxStalenessMs)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, summary.AllowedRequests, int64(0))
|
||||
require.GreaterOrEqual(t, summary.DeniedRequests, int64(0))
|
||||
|
||||
@@ -17,8 +17,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"cdr.dev/slog/v3/sloggers/slogtest"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/coderd/agentapi"
|
||||
"github.com/coder/coder/v2/coderd/boundaryusage"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
@@ -969,4 +973,71 @@ func TestTelemetry_BoundaryUsageSummary(t *testing.T) {
|
||||
// claimed the lock for this period.
|
||||
require.Nil(t, snapshot2.BoundaryUsageSummary)
|
||||
})
|
||||
|
||||
t.Run("WithBoundaryLogsAPI", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
// Create a tracker and BoundaryLogsAPI to simulate the full production
|
||||
// path: Agent -> ReportBoundaryLogs -> Tracker -> DB -> Telemetry.
|
||||
tracker := boundaryusage.NewTracker()
|
||||
replicaID := uuid.New()
|
||||
|
||||
workspace1, workspace2 := uuid.New(), uuid.New()
|
||||
owner1, owner2 := uuid.New(), uuid.New()
|
||||
|
||||
api1 := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil),
|
||||
WorkspaceID: workspace1,
|
||||
OwnerID: owner1,
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
api2 := &agentapi.BoundaryLogsAPI{
|
||||
Log: slogtest.Make(t, nil),
|
||||
WorkspaceID: workspace2,
|
||||
OwnerID: owner2,
|
||||
TemplateID: uuid.New(),
|
||||
TemplateVersionID: uuid.New(),
|
||||
BoundaryUsageTracker: tracker,
|
||||
}
|
||||
|
||||
// Simulate boundary logs from workspace 1: 1 allowed, 2 denied.
|
||||
req1 := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://a.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked1.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked2.com"}}},
|
||||
},
|
||||
}
|
||||
_, err := api1.ReportBoundaryLogs(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate boundary logs from workspace 2: 3 allowed, 1 denied.
|
||||
req2 := &agentproto.ReportBoundaryLogsRequest{
|
||||
Logs: []*agentproto.BoundaryLog{
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://f.com"}}},
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://g.com"}}},
|
||||
{Allowed: true, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://h.com"}}},
|
||||
{Allowed: false, Time: timestamppb.Now(), Resource: &agentproto.BoundaryLog_HttpRequest_{HttpRequest: &agentproto.BoundaryLog_HttpRequest{Method: "GET", Url: "https://blocked3.com"}}},
|
||||
},
|
||||
}
|
||||
_, err = api2.ReportBoundaryLogs(ctx, req2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tracker.FlushToDB(ctx, db, replicaID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, snapshot := collectSnapshot(ctx, t, db, nil)
|
||||
|
||||
require.NotNil(t, snapshot.BoundaryUsageSummary)
|
||||
require.Equal(t, int64(2), snapshot.BoundaryUsageSummary.UniqueWorkspaces)
|
||||
require.Equal(t, int64(2), snapshot.BoundaryUsageSummary.UniqueUsers)
|
||||
require.Equal(t, int64(1+3), snapshot.BoundaryUsageSummary.AllowedRequests)
|
||||
require.Equal(t, int64(2+1), snapshot.BoundaryUsageSummary.DeniedRequests)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user