-
Notifications
You must be signed in to change notification settings - Fork 96
feat: add dns-dedup E2E sample app #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #!/bin/bash | ||
| # Traffic generation for dns-dedup E2E test. | ||
| # Exercises single and bulk DNS resolution endpoints. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| BASE="http://localhost:8086" | ||
|
|
||
| echo "=== Single resolve (default domain) ===" | ||
| curl -sS "$BASE/resolve" | ||
| echo | ||
|
|
||
| echo "=== Single resolve (google.com) ===" | ||
| curl -sS "$BASE/resolve?domain=google.com" | ||
| echo | ||
|
|
||
| echo "=== Bulk resolve: 30 lookups for default domain ===" | ||
| curl -sS "$BASE/resolve-many?n=30" | ||
| echo | ||
|
|
||
| echo "=== Bulk resolve: 10 lookups for google.com ===" | ||
| curl -sS "$BASE/resolve-many?n=10&domain=google.com" | ||
| echo | ||
|
|
||
| echo "=== Done ===" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module dns-dedup | ||
|
|
||
| go 1.22.0 |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||
| package main | ||||||
|
|
||||||
| import ( | ||||||
| "encoding/json" | ||||||
| "fmt" | ||||||
| "net" | ||||||
| "net/http" | ||||||
| "os" | ||||||
| "strconv" | ||||||
| "time" | ||||||
| ) | ||||||
|
|
||||||
| // This app tests that Keploy properly deduplicates DNS mocks when the same | ||||||
| // domain returns different IPs on each lookup (round-robin / load-balancing). | ||||||
| // | ||||||
| // AWS services like SQS rotate IPs per DNS query. Before the fix, Keploy | ||||||
| // recorded a new DNS mock for every unique IP set, resulting in thousands of | ||||||
| // duplicate DNS mocks for a single domain. | ||||||
| // | ||||||
| // The /resolve-many endpoint triggers many DNS lookups for the same domain, | ||||||
| // which is the key scenario for verifying deduplication. | ||||||
|
|
||||||
| func main() { | ||||||
| domain := "sqs.us-east-1.amazonaws.com" | ||||||
| if len(os.Args) > 1 { | ||||||
| domain = os.Args[1] | ||||||
| } | ||||||
|
|
||||||
| http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { | ||||||
| w.WriteHeader(http.StatusOK) | ||||||
| fmt.Fprint(w, "ok") | ||||||
| }) | ||||||
|
|
||||||
| // Single DNS lookup | ||||||
| http.HandleFunc("/resolve", func(w http.ResponseWriter, r *http.Request) { | ||||||
| d := r.URL.Query().Get("domain") | ||||||
| if d == "" { | ||||||
| d = domain | ||||||
| } | ||||||
| ips, err := net.LookupHost(d) | ||||||
|
Comment on lines
+36
to
+40
|
||||||
| if err != nil { | ||||||
| http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
| return | ||||||
| } | ||||||
| w.Header().Set("Content-Type", "application/json") | ||||||
| json.NewEncoder(w).Encode(map[string]interface{}{ | ||||||
| "domain": d, | ||||||
| "ips": ips, | ||||||
| }) | ||||||
| }) | ||||||
|
|
||||||
| // Many DNS lookups for the same domain — the key dedup scenario. | ||||||
| // Without dedup, each unique IP set becomes a separate DNS mock. | ||||||
| http.HandleFunc("/resolve-many", func(w http.ResponseWriter, r *http.Request) { | ||||||
| d := r.URL.Query().Get("domain") | ||||||
| if d == "" { | ||||||
| d = domain | ||||||
| } | ||||||
|
Comment on lines
+55
to
+58
|
||||||
| n := 20 | ||||||
| if ns := r.URL.Query().Get("n"); ns != "" { | ||||||
| if parsed, err := strconv.Atoi(ns); err == nil && parsed > 0 { | ||||||
| n = parsed | ||||||
| } | ||||||
| } | ||||||
|
Comment on lines
+59
to
+64
|
||||||
|
|
||||||
| seen := make(map[string]bool) | ||||||
| type result struct { | ||||||
| Iteration int `json:"iteration"` | ||||||
| IPs []string `json:"ips,omitempty"` | ||||||
| New bool `json:"new"` | ||||||
| Error string `json:"error,omitempty"` | ||||||
| } | ||||||
| results := make([]result, 0, n) | ||||||
|
|
||||||
| for i := 1; i <= n; i++ { | ||||||
| ips, err := net.LookupHost(d) | ||||||
| if err != nil { | ||||||
| results = append(results, result{Iteration: i, Error: err.Error()}) | ||||||
| } else { | ||||||
| key := fmt.Sprintf("%v", ips) | ||||||
| isNew := !seen[key] | ||||||
| seen[key] = true | ||||||
| results = append(results, result{Iteration: i, IPs: ips, New: isNew}) | ||||||
|
Comment on lines
+80
to
+83
|
||||||
| } | ||||||
| time.Sleep(50 * time.Millisecond) | ||||||
| } | ||||||
|
|
||||||
| w.Header().Set("Content-Type", "application/json") | ||||||
| json.NewEncoder(w).Encode(map[string]interface{}{ | ||||||
| "domain": d, | ||||||
| "total_queries": n, | ||||||
| "unique_ip_sets": len(seen), | ||||||
| "results": results, | ||||||
| }) | ||||||
| }) | ||||||
|
|
||||||
| port := "8086" | ||||||
| fmt.Printf("DNS dedup test server starting on :%s\n", port) | ||||||
| if err := http.ListenAndServe(":"+port, nil); err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "server error: %v\n", err) | ||||||
|
||||||
| fmt.Fprintf(os.Stderr, "server error: %v\n", err) | |
| fmt.Fprintf(os.Stderr, "server failed to start on :%s: %v\nHint: Check if port %s is already in use or if this process has permission to bind it.\n", port, err, port) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For easier reuse in CI/local runs, consider allowing the base URL to be overridden via an environment variable (similar to other scripts in this repo), instead of hardcoding localhost:8086.