Ein Driver-Interface, zwei austauschbare Treiber: LocalDriver (Entwicklung, Dateisystem, HMAC-signierte URLs) und S3Driver (Produktion, S3-kompatibel via aws-sdk-go-v2, echte presigned URLs). Pfadschema documents/<id>/ revisions/<id> innerhalb des mandantenspezifischen Buckets. Service verbindet Driver mit Nutzungsmeldung an Core LIC-05 (HTTPUsageReporter, Vertrag von internal/resync.Handler nachgebildet, DMS kann Cores internal/- Pakete als eigenes Modul nicht importieren). Auf 192.168.1.131 verifiziert, S3-Treiber gegen echtes lokal installiertes MinIO (kein Mock): Round-Trip beide Treiber, abgelaufene presigned URL real mit 403 abgewiesen (manuell zusaetzlich zum Unit-Test verifiziert), klare ErrNotFound bei fehlendem Objekt beide Treiber, Nutzungsmeldung mit korrektem Tenant/Metrik/Delta bei Put/Delete. Befund dokumentiert: Cores internal/resync.Handler (Gegenstelle fuer die Nutzungsmeldung) ist noch in keinem cmd/*/main.go verdrahtet (dieselbe Fehlerklasse wie QA-05/AUD-06) - HTTPUsageReporter daher gegen den dokumentierten Vertrag getestet, nicht gegen eine laufende Core-Instanz. Siehe dms/docs/FDN-03-PRUEFPROTOKOLL.md. golangci-lint auf v2.1.6 aktualisiert (v1.63.4 konnte go1.24-Zielstand nicht linten), .golangci.yml auf v2-Konfigurationsformat migriert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
76 lines
2.7 KiB
Go
76 lines
2.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// UsageMetric ist der Metrikname, unter dem Core (internal/usage, LIC-05)
|
|
// den Speicherverbrauch je Mandant fuehrt — muss exakt
|
|
// internal/usage.StorageBytesMetric aus dem NEXARCH-Core-Modul entsprechen
|
|
// (Core kann von DMS als eigenem Go-Modul nicht importiert werden, daher
|
|
// hier als Konstante gespiegelt statt importiert).
|
|
const UsageMetric = "storage_bytes"
|
|
|
|
// UsageReporter meldet Speicherverbrauchsaenderungen an Core (Akzeptanz-
|
|
// kriterium 4). Schmale Schnittstelle, damit Tests einen Fake statt eines
|
|
// echten HTTP-Aufrufs einsetzen koennen.
|
|
type UsageReporter interface {
|
|
Report(ctx context.Context, tenantSlug, metric string, delta int64) error
|
|
}
|
|
|
|
// usageDeltaDTO entspricht Core internal/resync.usageDeltaDTO
|
|
// (JSON-Vertrag: tenant_slug/metric/delta) — dieselbe Struktur, hier
|
|
// gespiegelt, da DMS Cores internal/-Pakete nicht importieren kann.
|
|
type usageDeltaDTO struct {
|
|
TenantSlug string `json:"tenant_slug"`
|
|
Metric string `json:"metric"`
|
|
Delta int64 `json:"delta"`
|
|
}
|
|
|
|
// HTTPUsageReporter meldet ueber Cores Resync-Nutzungs-Endpunkt
|
|
// (internal/resync.Handler.UsageHandler, API-06), authentifiziert ueber
|
|
// dasselbe Service-Credential-Verfahren wie jeder andere Modul-Core-Aufruf
|
|
// (API-02).
|
|
type HTTPUsageReporter struct {
|
|
endpointURL string
|
|
clientID string
|
|
clientSecret string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewHTTPUsageReporter(endpointURL, clientID, clientSecret string, httpClient *http.Client) *HTTPUsageReporter {
|
|
if httpClient == nil {
|
|
httpClient = http.DefaultClient
|
|
}
|
|
return &HTTPUsageReporter{endpointURL: endpointURL, clientID: clientID, clientSecret: clientSecret, httpClient: httpClient}
|
|
}
|
|
|
|
func (r *HTTPUsageReporter) Report(ctx context.Context, tenantSlug, metric string, delta int64) error {
|
|
body, err := json.Marshal([]usageDeltaDTO{{TenantSlug: tenantSlug, Metric: metric, Delta: delta}})
|
|
if err != nil {
|
|
return fmt.Errorf("storage: nutzungsmeldung serialisieren: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.endpointURL, bytes.NewReader(body))
|
|
if err != nil {
|
|
return fmt.Errorf("storage: nutzungsmeldungs-anfrage aufbauen: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Nexarch-Client-Id", r.clientID)
|
|
req.Header.Set("X-Nexarch-Client-Secret", r.clientSecret)
|
|
|
|
resp, err := r.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("storage: nutzungsmeldung senden: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("storage: nutzungsmeldung von core abgelehnt: status %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|