feat(runtime-logs): enhance auto-cleanup features and documentation
CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 26s
CI / web (push) Successful in 33s
CI / go (push) Successful in 56s
CI / bird2 (push) Successful in 14s
CI / release (push) Successful in 20s
CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 26s
CI / web (push) Successful in 33s
CI / go (push) Successful in 56s
CI / bird2 (push) Successful in 14s
CI / release (push) Successful in 20s
Added new endpoints for estimating and executing runtime log auto-cleanup based on tenant settings. Introduced configuration options for auto-cleanup policies, including scheduling and file size limits. Updated the API documentation and UI components to reflect these changes, improving user interaction with runtime log management. Enhanced error handling and added new UI elements for better visibility of audit logs and cleanup actions.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"evobgp/internal/importer"
|
||||
"evobgp/internal/pipeline"
|
||||
"evobgp/internal/runtimelogs"
|
||||
"evobgp/internal/store"
|
||||
)
|
||||
|
||||
@@ -1117,6 +1118,10 @@ func (s *Server) handlePatchSettings(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
body["revision_retention_minutes"] = v
|
||||
}
|
||||
if !runtimelogs.ValidateRuntimeLogsSettingsPatch(body) {
|
||||
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "invalid runtime_logs_* settings")
|
||||
return
|
||||
}
|
||||
if err := s.store.PatchGlobalSettings(a.TenantID, body); err != nil {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
|
||||
@@ -14,6 +14,8 @@ func (s *Server) registerRuntimeLogsRoutes(m *http.ServeMux) {
|
||||
m.HandleFunc("GET /runtime-logs/files/{filename}", s.handleGetRuntimeLogTail)
|
||||
m.HandleFunc("DELETE /runtime-logs/files/{filename}", s.handleDeleteRuntimeLogFile)
|
||||
m.HandleFunc("GET /runtime-logs/cleanup-audit", s.handleListRuntimeLogCleanupAudit)
|
||||
m.HandleFunc("GET /runtime-logs/auto-estimate", s.handleRuntimeLogAutoEstimate)
|
||||
m.HandleFunc("POST /runtime-logs/auto-run", s.handleRuntimeLogAutoRun)
|
||||
}
|
||||
|
||||
func (s *Server) requireRuntimeLogs(w http.ResponseWriter) bool {
|
||||
@@ -144,6 +146,79 @@ func (s *Server) handleDeleteRuntimeLogFile(w http.ResponseWriter, r *http.Reque
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (s *Server) runtimeLogAutoPolicy(w http.ResponseWriter, r *http.Request, tenantID string) (runtimelogs.AutoPolicy, bool) {
|
||||
settings, err := s.store.ListGlobalSettings(tenantID)
|
||||
if err != nil {
|
||||
writeInternalError(w, "runtime_logs_policy", err)
|
||||
return runtimelogs.AutoPolicy{}, false
|
||||
}
|
||||
return runtimelogs.PolicyFromSettings(settings), true
|
||||
}
|
||||
|
||||
func (s *Server) handleRuntimeLogAutoEstimate(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok || !s.requireAtLeast(w, a, "operator") || !s.requireRuntimeLogs(w) {
|
||||
return
|
||||
}
|
||||
policy, ok := s.runtimeLogAutoPolicy(w, r, a.TenantID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := runtimelogs.EstimateAutoCleanup(s.runtimeLogs, policy)
|
||||
if err != nil {
|
||||
writeRuntimeLogsErr(w, "runtime_logs_auto_estimate", err)
|
||||
return
|
||||
}
|
||||
out := make([]map[string]any, 0, len(items))
|
||||
var wouldCount int
|
||||
for _, it := range items {
|
||||
if it.WouldCleanup {
|
||||
wouldCount++
|
||||
}
|
||||
row := map[string]any{
|
||||
"filename": it.Filename,
|
||||
"size_bytes": it.SizeBytes,
|
||||
"would_cleanup": it.WouldCleanup,
|
||||
}
|
||||
if it.SkipReason != "" {
|
||||
row["skip_reason"] = it.SkipReason
|
||||
}
|
||||
out = append(out, row)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"policy": map[string]any{
|
||||
"enabled": policy.Enabled,
|
||||
"max_file_bytes": policy.MaxFileBytes,
|
||||
"schedule": policy.Schedule,
|
||||
"mode": policy.Mode,
|
||||
},
|
||||
"items": out,
|
||||
"would_count": wouldCount,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleRuntimeLogAutoRun(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok || !s.requireAtLeast(w, a, "operator") || !s.requireRuntimeLogs(w) {
|
||||
return
|
||||
}
|
||||
policy, ok := s.runtimeLogAutoPolicy(w, r, a.TenantID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
dryRun := r.URL.Query().Get("dry_run") == "true"
|
||||
result, err := runtimelogs.RunAutoCleanup(r.Context(), runtimelogs.AutoCleanupDeps{
|
||||
Service: s.runtimeLogs,
|
||||
Store: s.store,
|
||||
TenantID: a.TenantID,
|
||||
}, policy, dryRun, "manual")
|
||||
if err != nil {
|
||||
writeRuntimeLogsErr(w, "runtime_logs_auto_run", err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (s *Server) handleListRuntimeLogCleanupAudit(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok || !s.requireAtLeast(w, a, "viewer") {
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestRuntimeLogsHappyPath(t *testing.T) {
|
||||
ServiceName: runtimelogs.ServiceNameAll,
|
||||
})
|
||||
handler := srv.Handler()
|
||||
tenant := "00000000-0000-0000-0000-000000000001"
|
||||
tenant, _, _, _, _ := srv.Store().DemoIDs()
|
||||
mustSetTestAPIKeys(t, srv, "vwkey|"+tenant+"|viewer,opkey|"+tenant+"|operator")
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
@@ -146,4 +146,46 @@ func TestRuntimeLogsHappyPath(t *testing.T) {
|
||||
t.Fatalf("expected audit entry, body=%s", auditRec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auto estimate and run", func(t *testing.T) {
|
||||
bigPath := filepath.Join(dir, "big.log")
|
||||
if err := os.WriteFile(bigPath, make([]byte, 2*1024*1024), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := srv.store.PatchGlobalSettings(tenant, map[string]any{
|
||||
runtimelogs.KeyMaxFileMB: 1,
|
||||
runtimelogs.KeyAutoMode: "truncate",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
estReq := httptest.NewRequest(http.MethodGet, "/v1/runtime-logs/auto-estimate", nil)
|
||||
estReq.Header.Set("Authorization", "Bearer opkey")
|
||||
estRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(estRec, estReq)
|
||||
if estRec.Code != http.StatusOK {
|
||||
t.Fatalf("estimate status=%d body=%s", estRec.Code, estRec.Body.String())
|
||||
}
|
||||
if !strings.Contains(estRec.Body.String(), `"would_count":1`) {
|
||||
t.Fatalf("expected would_count=1, body=%s", estRec.Body.String())
|
||||
}
|
||||
|
||||
runReq := httptest.NewRequest(http.MethodPost, "/v1/runtime-logs/auto-run", nil)
|
||||
runReq.Header.Set("Authorization", "Bearer opkey")
|
||||
runRec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(runRec, runReq)
|
||||
if runRec.Code != http.StatusOK {
|
||||
t.Fatalf("auto-run status=%d body=%s", runRec.Code, runRec.Body.String())
|
||||
}
|
||||
if !strings.Contains(runRec.Body.String(), "auto:scheduler") && !strings.Contains(runRec.Body.String(), `"cleaned_count":1`) {
|
||||
t.Fatalf("unexpected auto-run body=%s", runRec.Body.String())
|
||||
}
|
||||
st, err := os.Stat(bigPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Size() != 0 {
|
||||
t.Fatalf("expected truncated big.log, size=%d", st.Size())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+34
-23
@@ -21,18 +21,19 @@ import (
|
||||
|
||||
// Server implements EvoBGP control-plane HTTP API.
|
||||
type Server struct {
|
||||
store store.Backend
|
||||
pgPool *pgxpool.Pool
|
||||
pgMonitor *pgmonitor.Service
|
||||
maintConfig *maintenance.ConfigProvider
|
||||
maintStats *maintenance.DBStatsProvider
|
||||
jobs *jobs.Registry
|
||||
bundlePriv ed25519.PrivateKey
|
||||
keyResolver *apiKeyResolver
|
||||
corsOrigins []string
|
||||
cdnHTTP *http.Client
|
||||
runtimeLogs *runtimelogs.Service
|
||||
mux *http.ServeMux
|
||||
store store.Backend
|
||||
pgPool *pgxpool.Pool
|
||||
pgMonitor *pgmonitor.Service
|
||||
maintConfig *maintenance.ConfigProvider
|
||||
maintStats *maintenance.DBStatsProvider
|
||||
jobs *jobs.Registry
|
||||
bundlePriv ed25519.PrivateKey
|
||||
keyResolver *apiKeyResolver
|
||||
corsOrigins []string
|
||||
cdnHTTP *http.Client
|
||||
runtimeLogs *runtimelogs.Service
|
||||
runtimeLogsPolicyTenant string
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
// Options configures the API server.
|
||||
@@ -44,6 +45,8 @@ type Options struct {
|
||||
SeedDemo bool
|
||||
BundleSeedHex string
|
||||
CORSAllowedOrigins string
|
||||
// RuntimeLogsPolicyTenant overrides tenant for auto-cleanup scheduler settings (optional).
|
||||
RuntimeLogsPolicyTenant string
|
||||
}
|
||||
|
||||
// New constructs Server and wiring for async jobs.
|
||||
@@ -81,17 +84,18 @@ func New(opts Options) (*Server, error) {
|
||||
maintStats = maintenance.NewDBStatsProvider(pgMon)
|
||||
}
|
||||
s := &Server{
|
||||
store: backend,
|
||||
pgPool: pool,
|
||||
pgMonitor: pgMon,
|
||||
maintConfig: maintCfg,
|
||||
maintStats: maintStats,
|
||||
jobs: reg,
|
||||
bundlePriv: priv,
|
||||
keyResolver: resolver,
|
||||
corsOrigins: parseCORSOrigins(opts.CORSAllowedOrigins),
|
||||
cdnHTTP: NewCDNHTTPClient(),
|
||||
runtimeLogs: runtimelogs.NewService(runtimelogs.ConfigFromEnv()),
|
||||
store: backend,
|
||||
pgPool: pool,
|
||||
pgMonitor: pgMon,
|
||||
maintConfig: maintCfg,
|
||||
maintStats: maintStats,
|
||||
jobs: reg,
|
||||
bundlePriv: priv,
|
||||
keyResolver: resolver,
|
||||
corsOrigins: parseCORSOrigins(opts.CORSAllowedOrigins),
|
||||
cdnHTTP: NewCDNHTTPClient(),
|
||||
runtimeLogs: runtimelogs.NewService(runtimelogs.ConfigFromEnv()),
|
||||
runtimeLogsPolicyTenant: strings.TrimSpace(opts.RuntimeLogsPolicyTenant),
|
||||
}
|
||||
s.mux = http.NewServeMux()
|
||||
s.registerRoutes()
|
||||
@@ -126,4 +130,11 @@ func (s *Server) StartBackground(ctx context.Context) {
|
||||
})
|
||||
}, 30*time.Second)
|
||||
}
|
||||
if s != nil && s.runtimeLogs != nil && s.store != nil {
|
||||
runtimelogs.StartAutoCleanupScheduler(ctx, runtimelogs.SchedulerDeps{
|
||||
Service: s.runtimeLogs,
|
||||
Store: s.store,
|
||||
PolicyTenant: s.runtimeLogsPolicyTenant,
|
||||
}, 30*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user