From e2c626bb8a8b9eb7f496efd1123d58db066c050f Mon Sep 17 00:00:00 2001 From: Aditya Maru Date: Tue, 26 Nov 2024 12:25:41 -0500 Subject: [PATCH] cmd: add liveness reporter to ping our backend --- cmd/liveness_reporter.go | 82 ++++++++++++++++++++++++++++++++++++++++ cmd/server.go | 12 ++++++ 2 files changed, 94 insertions(+) create mode 100644 cmd/liveness_reporter.go diff --git a/cmd/liveness_reporter.go b/cmd/liveness_reporter.go new file mode 100644 index 0000000..cda7b5e --- /dev/null +++ b/cmd/liveness_reporter.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/rand" + "net/http" + "time" +) + +type LivenessReporter struct { + client http.Client + backendEndpoint string + backendAdminToken string + ip string + region string +} + +func (r *LivenessReporter) Report(ctx context.Context) { + requestBody := struct { + IP string `json:"ip"` + Region string `json:"region"` + }{ + IP: r.ip, + Region: r.region, + } + jsonBody, err := json.Marshal(requestBody) + if err != nil { + fmt.Printf("failed to marshal request body: %v\n", err) + return + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/staticip/liveness", r.backendEndpoint), bytes.NewBuffer(jsonBody)) + if err != nil { + fmt.Printf("failed to create request: %v\n", err) + return + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.backendAdminToken)) + + resp, err := r.client.Do(req) + if err != nil { + fmt.Printf("failed to send request: %v\n", err) + return + } + defer func() { + if err := resp.Body.Close(); err != nil { + fmt.Printf("failed to close response body: %v\n", err) + } + }() + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + fmt.Printf("error reporting liveness: unexpected status code: %d\n", resp.StatusCode) + } +} + +const ( + minLivenessReportInterval = 1500 * time.Millisecond + maxLivenessReportInterval = 2 * time.Second +) + +func (r *LivenessReporter) Start(ctx context.Context) { + go func() { + ticker := time.NewTicker(r.randomInterval()) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + r.Report(ctx) + ticker.Reset(r.randomInterval()) + case <-ctx.Done(): + return + } + } + }() +} + +func (r *LivenessReporter) randomInterval() time.Duration { + return time.Duration(rand.Int63n(int64(maxLivenessReportInterval-minLivenessReportInterval))) + minLivenessReportInterval +} diff --git a/cmd/server.go b/cmd/server.go index 2cf3b87..71dda5d 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "net/http" "net/netip" + "os" "os/signal" "strconv" "syscall" @@ -115,6 +117,16 @@ func runServer(cmd *cobra.Command, args []string) error { defer sm.Wait() defer done() + // Start the liveness reporter. + livenessReporter := &LivenessReporter{ + client: http.Client{Timeout: 5 * time.Second}, + ip: serverCmdArgs.ip[0], + region: serverCmdArgs.region, + backendEndpoint: os.Getenv("BACKEND_ENDPOINT"), + backendAdminToken: os.Getenv("BACKEND_ADMIN_TOKEN"), + } + livenessReporter.Start(ctx) + for _, ipStr := range serverCmdArgs.ip { ip, err := netip.ParseAddr(ipStr) if err != nil || !ip.Is4() {