mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-04 06:20:14 +00:00
feat: Pulse v6 release
This commit is contained in:
parent
2fe22c3308
commit
778a2577b6
3155 changed files with 594553 additions and 173975 deletions
247
pkg/auth/sqlite_manager_bench_test.go
Normal file
247
pkg/auth/sqlite_manager_bench_test.go
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// newBenchManager creates an ephemeral SQLiteManager pre-populated with
|
||||
// built-in roles. Caller must close via b.Cleanup.
|
||||
func newBenchManager(b *testing.B) *SQLiteManager {
|
||||
b.Helper()
|
||||
dir := b.TempDir()
|
||||
m, err := NewSQLiteManager(SQLiteManagerConfig{DataDir: dir})
|
||||
if err != nil {
|
||||
b.Fatalf("NewSQLiteManager: %v", err)
|
||||
}
|
||||
b.Cleanup(func() { m.Close() })
|
||||
return m
|
||||
}
|
||||
|
||||
// seedBenchUsers creates numUsers users, each assigned to one of the
|
||||
// built-in roles in a round-robin pattern (admin, operator, viewer, auditor).
|
||||
func seedBenchUsers(b *testing.B, m *SQLiteManager, numUsers int) []string {
|
||||
b.Helper()
|
||||
roles := []string{RoleAdmin, RoleOperator, RoleViewer, RoleAuditor}
|
||||
users := make([]string, numUsers)
|
||||
for i := 0; i < numUsers; i++ {
|
||||
username := fmt.Sprintf("bench-user-%04d", i)
|
||||
users[i] = username
|
||||
roleID := roles[i%len(roles)]
|
||||
if err := m.AssignRole(username, roleID); err != nil {
|
||||
b.Fatalf("AssignRole(%s, %s): %v", username, roleID, err)
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
// seedBenchCustomRoles creates numRoles custom roles, each with numPerms
|
||||
// permissions. Returns the role IDs.
|
||||
func seedBenchCustomRoles(b *testing.B, m *SQLiteManager, numRoles, numPerms int) []string {
|
||||
b.Helper()
|
||||
actions := []string{"read", "write", "delete"}
|
||||
resources := []string{"nodes", "vms", "containers", "alerts", "settings", "ai", "discovery", "audit_logs"}
|
||||
ids := make([]string, numRoles)
|
||||
for i := 0; i < numRoles; i++ {
|
||||
id := fmt.Sprintf("custom-role-%04d", i)
|
||||
ids[i] = id
|
||||
perms := make([]Permission, numPerms)
|
||||
for j := 0; j < numPerms; j++ {
|
||||
perms[j] = Permission{
|
||||
Action: actions[j%len(actions)],
|
||||
Resource: resources[j%len(resources)],
|
||||
Effect: EffectAllow,
|
||||
}
|
||||
}
|
||||
if err := m.SaveRole(Role{
|
||||
ID: id,
|
||||
Name: fmt.Sprintf("Custom Role %d", i),
|
||||
Description: "Benchmark custom role",
|
||||
Permissions: perms,
|
||||
}); err != nil {
|
||||
b.Fatalf("SaveRole(%s): %v", id, err)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// BenchmarkGetUserPermissions measures the hot path of resolving a user's
|
||||
// effective permissions from SQLite. This is called on every permissioned
|
||||
// API request via the PolicyEvaluator → SQLiteManager chain.
|
||||
func BenchmarkGetUserPermissions(b *testing.B) {
|
||||
for _, numUsers := range []int{10, 50, 200} {
|
||||
b.Run(fmt.Sprintf("users=%d", numUsers), func(b *testing.B) {
|
||||
m := newBenchManager(b)
|
||||
users := seedBenchUsers(b, m, numUsers)
|
||||
|
||||
// Verify permissions are returned (guard against silent regressions).
|
||||
perms := m.GetUserPermissions(users[0])
|
||||
if len(perms) == 0 {
|
||||
b.Fatalf("expected non-empty permissions for %s", users[0])
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = m.GetUserPermissions(users[i%len(users)])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetRole measures single role lookup by ID — called during
|
||||
// permission resolution and role inheritance walks.
|
||||
func BenchmarkGetRole(b *testing.B) {
|
||||
m := newBenchManager(b)
|
||||
// Create 10 custom roles to add variety beyond built-ins.
|
||||
customIDs := seedBenchCustomRoles(b, m, 10, 6)
|
||||
|
||||
allRoleIDs := append([]string{RoleAdmin, RoleOperator, RoleViewer, RoleAuditor}, customIDs...)
|
||||
|
||||
// Verify every role is found (guard against silent regressions).
|
||||
for _, id := range allRoleIDs {
|
||||
if _, ok := m.GetRole(id); !ok {
|
||||
b.Fatalf("role %q not found", id)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, ok := m.GetRole(allRoleIDs[i%len(allRoleIDs)]); !ok {
|
||||
b.Fatal("unexpected miss in timed loop")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetUserAssignment measures user-to-role assignment lookup — a
|
||||
// prerequisite for every permission resolution.
|
||||
func BenchmarkGetUserAssignment(b *testing.B) {
|
||||
for _, numUsers := range []int{10, 100} {
|
||||
b.Run(fmt.Sprintf("users=%d", numUsers), func(b *testing.B) {
|
||||
m := newBenchManager(b)
|
||||
users := seedBenchUsers(b, m, numUsers)
|
||||
|
||||
// Verify assignments exist (guard against silent regressions).
|
||||
if _, ok := m.GetUserAssignment(users[0]); !ok {
|
||||
b.Fatalf("expected assignment for %s", users[0])
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, ok := m.GetUserAssignment(users[i%len(users)]); !ok {
|
||||
b.Fatal("unexpected miss in timed loop")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAuthorize measures the full RBAC authorization chain:
|
||||
// RBACAuthorizer → PolicyEvaluator → SQLiteManager. This is the complete
|
||||
// per-request hot path when RBAC is active. Contexts are prebuilt outside
|
||||
// the timed loop to isolate auth-chain cost from context allocation.
|
||||
func BenchmarkAuthorize(b *testing.B) {
|
||||
m := newBenchManager(b)
|
||||
users := seedBenchUsers(b, m, 50)
|
||||
authorizer := NewRBACAuthorizer(m)
|
||||
|
||||
// Prebuild contexts and request descriptors.
|
||||
type authReq struct {
|
||||
ctx context.Context
|
||||
action string
|
||||
resource string
|
||||
}
|
||||
requests := make([]authReq, len(users))
|
||||
for i, u := range users {
|
||||
requests[i] = authReq{
|
||||
ctx: WithUser(context.Background(), u),
|
||||
action: ActionRead,
|
||||
resource: ResourceNodes,
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the authorization path works.
|
||||
allowed, err := authorizer.Authorize(requests[0].ctx, ActionRead, ResourceNodes)
|
||||
if err != nil {
|
||||
b.Fatalf("Authorize error: %v", err)
|
||||
}
|
||||
// users[0] is assigned to admin (round-robin), admin has action=admin resource=*
|
||||
if !allowed {
|
||||
b.Fatal("expected user-0 (admin) to be allowed")
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
req := requests[i%len(requests)]
|
||||
authorizer.Authorize(req.ctx, req.action, req.resource)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetRolesWithInheritance measures the inheritance-aware role
|
||||
// resolution path. This exercises the recursive parent-chain walk that
|
||||
// occurs when the ExtendedManager interface is detected by PolicyEvaluator.
|
||||
func BenchmarkGetRolesWithInheritance(b *testing.B) {
|
||||
m := newBenchManager(b)
|
||||
|
||||
// Create a 3-level role hierarchy: base → mid → leaf.
|
||||
if err := m.SaveRole(Role{
|
||||
ID: "bench-base",
|
||||
Name: "Base",
|
||||
Description: "Base role",
|
||||
Permissions: []Permission{
|
||||
{Action: "read", Resource: "nodes"},
|
||||
{Action: "read", Resource: "vms"},
|
||||
},
|
||||
}); err != nil {
|
||||
b.Fatalf("SaveRole(base): %v", err)
|
||||
}
|
||||
if err := m.SaveRole(Role{
|
||||
ID: "bench-mid",
|
||||
Name: "Mid",
|
||||
Description: "Mid role",
|
||||
ParentID: "bench-base",
|
||||
Permissions: []Permission{
|
||||
{Action: "write", Resource: "nodes"},
|
||||
},
|
||||
}); err != nil {
|
||||
b.Fatalf("SaveRole(mid): %v", err)
|
||||
}
|
||||
if err := m.SaveRole(Role{
|
||||
ID: "bench-leaf",
|
||||
Name: "Leaf",
|
||||
Description: "Leaf role",
|
||||
ParentID: "bench-mid",
|
||||
Permissions: []Permission{
|
||||
{Action: "write", Resource: "alerts"},
|
||||
},
|
||||
}); err != nil {
|
||||
b.Fatalf("SaveRole(leaf): %v", err)
|
||||
}
|
||||
|
||||
// Assign users to the leaf role (triggers 3-level inheritance walk).
|
||||
const numUsers = 20
|
||||
users := make([]string, numUsers)
|
||||
for i := 0; i < numUsers; i++ {
|
||||
username := fmt.Sprintf("inherit-user-%04d", i)
|
||||
users[i] = username
|
||||
if err := m.AssignRole(username, "bench-leaf"); err != nil {
|
||||
b.Fatalf("AssignRole: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify inheritance resolves 3 roles.
|
||||
roles := m.GetRolesWithInheritance(users[0])
|
||||
if len(roles) != 3 {
|
||||
b.Fatalf("expected 3 inherited roles, got %d", len(roles))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.GetRolesWithInheritance(users[i%len(users)])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue