FDN-03: objekt-storage-abstraktion
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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9d4c2bae4a
commit
442bb674e9
@@ -0,0 +1,75 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user