Добавлены новые возможности для управления файловыми логами в Docker-сервисах: - Обновлены конфигурации для поддержки логов, включая переменные окружения и монтирование директорий. - Документация обновлена для описания новых эндпоинтов и параметров, связанных с логами. - Упрощен доступ к логам через API и интерфейс пользователя. Co-authored-by: Cursor <[email protected]>
150 lines
4.6 KiB
Go
150 lines
4.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"evobgp/internal/runtimelogs"
|
|
)
|
|
|
|
func TestRuntimeLogsFSUnavailable503(t *testing.T) {
|
|
srv, err := New(Options{SeedDemo: true, InsecureDev: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
handler := srv.Handler()
|
|
|
|
tests := []struct {
|
|
method string
|
|
path string
|
|
}{
|
|
{http.MethodGet, "/v1/runtime-logs/files"},
|
|
{http.MethodGet, "/v1/runtime-logs/files/evobgp-all.log"},
|
|
{http.MethodDelete, "/v1/runtime-logs/files/evobgp-all.log"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
|
req := httptest.NewRequest(tc.method, tc.path, nil)
|
|
req.Header.Set("Authorization", "Bearer dev")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Body.String(), "runtime_logs_unavailable") {
|
|
t.Fatalf("expected runtime_logs_unavailable detail, body=%s", rec.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuntimeLogsCleanupAuditWithoutFS(t *testing.T) {
|
|
srv, err := New(Options{SeedDemo: true, InsecureDev: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
handler := srv.Handler()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/runtime-logs/cleanup-audit", nil)
|
|
req.Header.Set("Authorization", "Bearer dev")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestRuntimeLogsHappyPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
logPath := filepath.Join(dir, "evobgp-all.log")
|
|
if err := os.WriteFile(logPath, []byte("line1\nline2\nline3\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv, err := New(Options{SeedDemo: true, InsecureDev: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
srv.runtimeLogs = runtimelogs.NewService(runtimelogs.Config{
|
|
RootDir: dir,
|
|
ServiceName: runtimelogs.ServiceNameAll,
|
|
})
|
|
handler := srv.Handler()
|
|
tenant := "00000000-0000-0000-0000-000000000001"
|
|
mustSetTestAPIKeys(t, srv, "vwkey|"+tenant+"|viewer,opkey|"+tenant+"|operator")
|
|
|
|
t.Run("list", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/runtime-logs/files", nil)
|
|
req.Header.Set("Authorization", "Bearer vwkey")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Body.String(), "evobgp-all.log") {
|
|
t.Fatalf("expected file in list, body=%s", rec.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("tail", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/runtime-logs/files/evobgp-all.log?lines=2", nil)
|
|
req.Header.Set("Authorization", "Bearer vwkey")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Body.String(), "line2") || !strings.Contains(rec.Body.String(), "line3") {
|
|
t.Fatalf("unexpected tail body=%s", rec.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("viewer cannot cleanup", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/runtime-logs/files/evobgp-all.log", nil)
|
|
req.Header.Set("Authorization", "Bearer vwkey")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
})
|
|
|
|
t.Run("cleanup truncate and audit", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/runtime-logs/files/evobgp-all.log?mode=truncate", nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Body.String(), `"action":"truncate"`) {
|
|
t.Fatalf("unexpected cleanup body=%s", rec.Body.String())
|
|
}
|
|
st, err := os.Stat(logPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st.Size() != 0 {
|
|
t.Fatalf("expected truncated file, size=%d", st.Size())
|
|
}
|
|
|
|
auditReq := httptest.NewRequest(http.MethodGet, "/v1/runtime-logs/cleanup-audit", nil)
|
|
auditReq.Header.Set("Authorization", "Bearer vwkey")
|
|
auditRec := httptest.NewRecorder()
|
|
handler.ServeHTTP(auditRec, auditReq)
|
|
if auditRec.Code != http.StatusOK {
|
|
t.Fatalf("audit status=%d body=%s", auditRec.Code, auditRec.Body.String())
|
|
}
|
|
if !strings.Contains(auditRec.Body.String(), "evobgp-all.log") {
|
|
t.Fatalf("expected audit entry, body=%s", auditRec.Body.String())
|
|
}
|
|
})
|
|
}
|