From b2c5ec27eb8783a2cce66a3edaec9c63868d1238 Mon Sep 17 00:00:00 2001 From: Justin Pierce Date: Fri, 13 Feb 2026 08:08:19 -0500 Subject: [PATCH] coverage_server changes for OCP --- server/coverage_server.go | 161 ++++++++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 50 deletions(-) diff --git a/server/coverage_server.go b/server/coverage_server.go index 4c1e856..08b8a30 100644 --- a/server/coverage_server.go +++ b/server/coverage_server.go @@ -1,19 +1,42 @@ package main +// All imports are aliased with a "_cov" prefix and +// all top-level identifiers use a "_cov" prefix to avoid name +// collisions with identifiers declared by the host package (e.g. many +// projects declare ``var log = …`` at the package level). +// +// Multiple containers in a pod may each start a coverage server. The server +// tries ports starting at _covDefaultPort (or COVERAGE_PORT) and increments +// up to _covMaxRetries times until it finds a free port. +// +// Clients can identify a coverage server by sending a HEAD request to any +// endpoint: the response will include the headers: +// X-Art-Coverage-Server: 1 +// X-Art-Coverage-Pid: +// X-Art-Coverage-Binary: + import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "runtime/coverage" - "time" + _covBytes "bytes" + _covBase64 "encoding/base64" + _covJSON "encoding/json" + _covFmt "fmt" + _covLog "log" + _covNet "net" + _covHTTP "net/http" + _covOS "os" + _covPath "path/filepath" + _covRuntime "runtime/coverage" + _covStrconv "strconv" + _covTime "time" +) + +const ( + _covDefaultPort = 53700 // Starting port for the coverage server + _covMaxRetries = 50 // Maximum number of ports to try ) -// CoverageResponse represents the JSON response from the coverage endpoint -type CoverageResponse struct { +// _covResponse represents the JSON response from the coverage endpoint +type _covResponse struct { MetaFilename string `json:"meta_filename"` MetaData string `json:"meta_data"` // base64 encoded CountersFilename string `json:"counters_filename"` @@ -23,51 +46,89 @@ type CoverageResponse struct { func init() { // Start coverage server in a separate goroutine - go startCoverageServer() + go _covStartServer() +} + +// _covIdentityMiddleware wraps a handler to add identification headers to +// every response (including HEAD requests) so that clients can confirm they +// are talking to a coverage server rather than an unrelated process. +func _covIdentityMiddleware(next _covHTTP.Handler) _covHTTP.Handler { + pid := _covStrconv.Itoa(_covOS.Getpid()) + exe := "unknown" + if exePath, err := _covOS.Executable(); err == nil { + exe = _covPath.Base(exePath) + } + return _covHTTP.HandlerFunc(func(w _covHTTP.ResponseWriter, r *_covHTTP.Request) { + w.Header().Set("X-Art-Coverage-Server", "1") + w.Header().Set("X-Art-Coverage-Pid", pid) + w.Header().Set("X-Art-Coverage-Binary", exe) + next.ServeHTTP(w, r) + }) } -// startCoverageServer starts a dedicated HTTP server for coverage collection -func startCoverageServer() { - // Get coverage port from environment variable, default to 9095 - coveragePort := os.Getenv("COVERAGE_PORT") - if coveragePort == "" { - coveragePort = "9095" +// _covStartServer starts a dedicated HTTP server for coverage collection. +// It tries successive ports starting from COVERAGE_PORT (default 53700) +// until one is available or _covMaxRetries attempts are exhausted. +func _covStartServer() { + startPort := _covDefaultPort + if envPort := _covOS.Getenv("COVERAGE_PORT"); envPort != "" { + if p, err := _covStrconv.Atoi(envPort); err == nil && p > 0 { + startPort = p + } } // Create a new ServeMux for the coverage server (isolated from main app) - mux := http.NewServeMux() - mux.HandleFunc("/coverage", CoverageHandler) - mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "coverage server healthy") + mux := _covHTTP.NewServeMux() + mux.HandleFunc("/coverage", _covHandler) + mux.HandleFunc("/health", func(w _covHTTP.ResponseWriter, r *_covHTTP.Request) { + w.WriteHeader(_covHTTP.StatusOK) + _covFmt.Fprintf(w, "coverage server healthy") }) - addr := ":" + coveragePort - log.Printf("[COVERAGE] Starting coverage server on %s", addr) - log.Printf("[COVERAGE] Endpoints: GET %s/coverage, GET %s/health", addr, addr) + // Wrap with identity middleware so HEAD requests get recognition headers + handler := _covIdentityMiddleware(mux) + + for attempt := 0; attempt < _covMaxRetries; attempt++ { + port := startPort + attempt + addr := _covFmt.Sprintf(":%d", port) + + // Try to bind the port before committing to ListenAndServe so we + // can detect "address already in use" and try the next port. + ln, err := _covNet.Listen("tcp", addr) + if err != nil { + _covLog.Printf("[COVERAGE] Port %d unavailable: %v; trying next", port, err) + continue + } - // Start the server (this will block, but we're in a goroutine) - if err := http.ListenAndServe(addr, mux); err != nil { - log.Printf("[COVERAGE] ERROR: Coverage server failed: %v", err) + _covLog.Printf("[COVERAGE] Starting coverage server on port %d (pid %d)", port, _covOS.Getpid()) + _covLog.Printf("[COVERAGE] Endpoints: GET %s/coverage, GET %s/health, HEAD %s/*", addr, addr, addr) + + // Serve on the already-bound listener (this blocks) + if err := _covHTTP.Serve(ln, handler); err != nil { + _covLog.Printf("[COVERAGE] ERROR: Coverage server on port %d failed: %v", port, err) + } + return } + + _covLog.Printf("[COVERAGE] ERROR: Could not bind any port in range %d–%d", startPort, startPort+_covMaxRetries-1) } -// CoverageHandler collects coverage data and returns it via HTTP as JSON -func CoverageHandler(w http.ResponseWriter, r *http.Request) { - log.Println("[COVERAGE] Collecting coverage data...") +// _covHandler collects coverage data and returns it via HTTP as JSON +func _covHandler(w _covHTTP.ResponseWriter, r *_covHTTP.Request) { + _covLog.Println("[COVERAGE] Collecting coverage data...") // Collect metadata - var metaBuf bytes.Buffer - if err := coverage.WriteMeta(&metaBuf); err != nil { - http.Error(w, fmt.Sprintf("Failed to collect metadata: %v", err), http.StatusInternalServerError) + var metaBuf _covBytes.Buffer + if err := _covRuntime.WriteMeta(&metaBuf); err != nil { + _covHTTP.Error(w, _covFmt.Sprintf("Failed to collect metadata: %v", err), _covHTTP.StatusInternalServerError) return } metaData := metaBuf.Bytes() // Collect counters - var counterBuf bytes.Buffer - if err := coverage.WriteCounters(&counterBuf); err != nil { - http.Error(w, fmt.Sprintf("Failed to collect counters: %v", err), http.StatusInternalServerError) + var counterBuf _covBytes.Buffer + if err := _covRuntime.WriteCounters(&counterBuf); err != nil { + _covHTTP.Error(w, _covFmt.Sprintf("Failed to collect counters: %v", err), _covHTTP.StatusInternalServerError) return } counterData := counterBuf.Bytes() @@ -76,34 +137,34 @@ func CoverageHandler(w http.ResponseWriter, r *http.Request) { var hash string if len(metaData) >= 32 { hashBytes := metaData[16:32] - hash = fmt.Sprintf("%x", hashBytes) + hash = _covFmt.Sprintf("%x", hashBytes) } else { hash = "unknown" } // Generate proper filenames - timestamp := time.Now().UnixNano() - metaFilename := fmt.Sprintf("covmeta.%s", hash) - counterFilename := fmt.Sprintf("covcounters.%s.%d.%d", hash, os.Getpid(), timestamp) + timestamp := _covTime.Now().UnixNano() + metaFilename := _covFmt.Sprintf("covmeta.%s", hash) + counterFilename := _covFmt.Sprintf("covcounters.%s.%d.%d", hash, _covOS.Getpid(), timestamp) - log.Printf("[COVERAGE] Collected %d bytes metadata, %d bytes counters", + _covLog.Printf("[COVERAGE] Collected %d bytes metadata, %d bytes counters", len(metaData), len(counterData)) // Return coverage data as JSON - response := CoverageResponse{ + response := _covResponse{ MetaFilename: metaFilename, - MetaData: base64.StdEncoding.EncodeToString(metaData), + MetaData: _covBase64.StdEncoding.EncodeToString(metaData), CountersFilename: counterFilename, - CountersData: base64.StdEncoding.EncodeToString(counterData), + CountersData: _covBase64.StdEncoding.EncodeToString(counterData), Timestamp: timestamp, } w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(response); err != nil { - log.Printf("[COVERAGE] Error encoding response: %v", err) - http.Error(w, "Failed to encode response", http.StatusInternalServerError) + if err := _covJSON.NewEncoder(w).Encode(response); err != nil { + _covLog.Printf("[COVERAGE] Error encoding response: %v", err) + _covHTTP.Error(w, "Failed to encode response", _covHTTP.StatusInternalServerError) return } - log.Println("[COVERAGE] Coverage data sent successfully") + _covLog.Println("[COVERAGE] Coverage data sent successfully") }