17 Commits

Author SHA1 Message Date
skamboj
7935a11f9d Merge pull request #164 from cooperlees/master
Add UDP probe metrics: packet loss, hop count, and RTT
2026-04-03 13:04:31 -04:00
Sachin Kamboj
de7f4e9004 Bump the version to 3.11.0
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-04-03 12:57:41 -04:00
Cooper Ry Lees
145d2bf000 Rename PathLength to HopCount in swagger model and UI
Rename the swagger field from path-length to hop-count so the
generated Go struct field (PathLength → HopCount) and JSON key
(path-length → hop-count) align with the Prometheus metric rename
to goldpinger_peers_hop_count from the previous commit.

Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:45:31 +00:00
Cooper Ry Lees
641b658f23 Address PR #164 review feedback
Concurrent HTTP + UDP pings:
  HTTP ping and UDP probe now run in separate goroutines via
  sync.WaitGroup, so UDP timeout doesn't add to the ping cycle
  latency. (skamboj on pinger.go:124)

Remove duplicate log:
  Removed the "UDP echo listener started" log from main.go since
  StartUDPListener already logs it. (skamboj on main.go:191)

Prometheus base units (seconds):
  Renamed goldpinger_peers_udp_rtt_ms back to goldpinger_peers_udp_rtt_s
  with sub-millisecond histogram buckets (.0001s to 1s), per Prometheus
  naming conventions. RTT is computed in seconds internally and only
  converted to ms for the JSON API. (skamboj on stats.go:150)

Rename path_length to hop_count:
  goldpinger_peers_path_length → goldpinger_peers_hop_count, and
  SetPeerPathLength → SetPeerHopCount. (skamboj on stats.go:139)

UDP buffer constant and packet size clamping:
  Added udpMaxPacketSize=1500 constant, documented as standard Ethernet
  MTU — the largest UDP payload that survives most networks without
  fragmentation. Used for both listener and prober receive buffers.
  ProbeUDP now clamps UDP_PACKET_SIZE to udpMaxPacketSize to prevent
  silent truncation if someone configures a size > MTU.
  (skamboj on udp_probe.go:54)

Guard count=0:
  ProbeUDP returns an error immediately if count <= 0 instead of
  dividing by zero. (skamboj on udp_probe.go:176)

UDP error counter:
  Added goldpinger_udp_errors_total counter (labels: goldpinger_instance,
  host). CountUDPError is called on dial failures and send errors.
  (skamboj on udp_probe.go:115)

Test: random source port for full loss:
  TestProbeUDP_FullLoss now binds an ephemeral port and closes it,
  instead of assuming port 19999 is free. (skamboj on udp_probe_test.go:56)

Test: partial loss validation:
  New TestProbeUDP_PartialLoss uses a lossy echo listener that drops
  every Nth packet to validate loss calculations are exact:
    drop every 2nd → 50.0%, every 3rd → 33.3%,
    every 5th → 20.0%, every 10th → 10.0%
  (skamboj on udp_probe_test.go:96)

Test: zero count:
  New TestProbeUDP_ZeroCount verifies error is returned for count=0.

Test results:
```
=== RUN   TestProbeUDP_NoLoss
    udp_probe_test.go:88: avg UDP RTT: 0.0816 ms
--- PASS: TestProbeUDP_NoLoss (0.00s)
=== RUN   TestProbeUDP_FullLoss
--- PASS: TestProbeUDP_FullLoss (0.00s)
=== RUN   TestProbeUDP_PartialLoss
=== RUN   TestProbeUDP_PartialLoss/drop_every_2nd_(50%)
    udp_probe_test.go:134: loss: 50.0% (expected 50.0%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_3rd_(33.3%)
    udp_probe_test.go:134: loss: 33.3% (expected 33.3%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_5th_(20%)
    udp_probe_test.go:134: loss: 20.0% (expected 20.0%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_10th_(10%)
    udp_probe_test.go:134: loss: 10.0% (expected 10.0%)
--- PASS: TestProbeUDP_PartialLoss (8.00s)
=== RUN   TestProbeUDP_ZeroCount
--- PASS: TestProbeUDP_ZeroCount (0.00s)
=== RUN   TestProbeUDP_PacketFormat
--- PASS: TestProbeUDP_PacketFormat (0.00s)
=== RUN   TestEstimateHops
--- PASS: TestEstimateHops (0.00s)
PASS
```

Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:37:52 +00:00
Cooper Ry Lees
832bc7b598 Add UDP probe metrics: packet loss, hop count, and RTT
Add an opt-in UDP echo probe that runs alongside the existing HTTP
ping. Each goldpinger pod listens on a configurable UDP port (default
6969). During each ping cycle, the prober sends N sequenced packets
to the peer's listener, which echoes them back. From the replies we
compute packet loss percentage, path hop count (from IPv4 TTL / IPv6
HopLimit), and average round-trip time.

New Prometheus metrics:
  - goldpinger_peers_loss_pct      (gauge)     — per-peer UDP loss %
  - goldpinger_peers_path_length   (gauge)     — estimated hop count
  - goldpinger_peers_udp_rtt_ms    (histogram) — UDP RTT in milliseconds

The graph UI shows yellow edges for links with partial loss, and
displays sub-millisecond UDP RTT instead of HTTP latency when UDP
is enabled. Stale metric labels are cleaned up when a pinger is
destroyed so rolled pods don't leave ghost entries.

Configuration (all via env vars, disabled by default):
  UDP_ENABLED=true      enable UDP probing and listener
  UDP_PORT=6969         listener port
  UDP_PACKET_COUNT=10   packets per probe
  UDP_PACKET_SIZE=64    bytes per packet
  UDP_TIMEOUT=1s        probe timeout

New files:
  pkg/goldpinger/udp_probe.go       — echo listener + probe client
  pkg/goldpinger/udp_probe_test.go  — unit tests

Unit tests:
```
=== RUN   TestProbeUDP_NoLoss
    udp_probe_test.go:51: avg UDP RTT: 0.0823 ms
--- PASS: TestProbeUDP_NoLoss (0.00s)
=== RUN   TestProbeUDP_FullLoss
--- PASS: TestProbeUDP_FullLoss (0.00s)
=== RUN   TestProbeUDP_PacketFormat
--- PASS: TestProbeUDP_PacketFormat (0.00s)
=== RUN   TestEstimateHops
--- PASS: TestEstimateHops (0.00s)
PASS
```

Cluster test (6-node IPv6 k8s, UDP_ENABLED=true):
```
Prometheus metrics (healthy cluster, 0% loss):
  goldpinger_peers_loss_pct{...,pod_ip="fd00:4:69:3::3746"} 0
  goldpinger_peers_path_length{...,pod_ip="fd00:4:69:3::3746"} 0

Simulated 50% loss via ip6tables DROP in pod netns on node-0:
  goldpinger_peers_loss_pct{instance="server",...} 60
  goldpinger_peers_loss_pct{instance="node-1",...} 30
  goldpinger_peers_loss_pct{instance="server2",...} 30

UDP RTT vs HTTP RTT (check_all API):
  node-0 -> server:  udp=2.18ms  http=2ms
  node-2 -> node-2:  udp=0.40ms  http=1ms
  server -> node-0:  udp=0.55ms  http=2ms

Post-rollout stale metrics cleanup verified:
  All 36 edges show 0% loss, no stale pod IPs.
```

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
2026-03-27 16:05:32 +00:00
skamboj
8d63d44fe2 Merge pull request #162 from skamboj/update-golang
Some checks failed
Helm Publish / helm_publish (push) Failing after 9s
CI / build (push) Successful in 5m56s
Update golang to 1.25 and update all dependencies
2026-01-28 21:12:18 -05:00
Sachin Kamboj
4392ae9f09 Update chart versions as well
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:37:47 -05:00
Sachin Kamboj
cb9c8ae248 Update goldpinger version
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:28:04 -05:00
Sachin Kamboj
b54e3feea6 Update dependencies
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:27:05 -05:00
Sachin Kamboj
0dfa55880c Update to golang 1.25
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:26:41 -05:00
skamboj
a93c8040a1 Merge pull request #161 from skamboj/update-workflows
All checks were successful
Helm Publish / helm_publish (push) Successful in 52s
CI / build (push) Successful in 6m19s
Update versions of the various actions
2026-01-28 20:11:48 -05:00
Sachin Kamboj
3ce341330b Attempt to fix the bake step
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 16:30:22 -05:00
Sachin Kamboj
a85572f799 More updates to the versions
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 08:50:01 -05:00
Sachin Kamboj
f29301ed41 Merge remote-tracking branch 'upstream/master' into update-workflows
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 08:37:45 -05:00
Sachin Kamboj
7379914781 Update versions of the various actions
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-27 21:35:26 -05:00
skamboj
52e86c25f5 Merge pull request #152 from Leundai/add-deepwiki
Some checks failed
Helm Publish / helm_publish (push) Successful in 55s
CI / build (push) Failing after 47m53s
feat: Add deepwiki badge
2025-10-04 11:21:14 -04:00
leundai
ba779f50e7 feat: Add deepwiki badge
Small enhancement to improve quick onboarding for the curious

Signed-off-by: leundai <leogalindofrias@gmail.com>
2025-07-12 14:48:02 -04:00
70 changed files with 1555 additions and 432 deletions

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -28,6 +28,6 @@ jobs:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
uses: helm/chart-releaser-action@v1.7.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -6,12 +6,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v6
with:
go-version: 1.22
go-version-file: 'go.mod'
- name: Install Tools
shell: bash
@@ -67,7 +67,7 @@ jobs:
- name: Upload Tilt Snapshot
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v6
with:
name: tilt-snapshot
path: |

View File

@@ -15,9 +15,9 @@ jobs:
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
@@ -45,7 +45,6 @@ jobs:
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -63,13 +62,13 @@ jobs:
org.opencontainers.image.vendor=${{ github.repository_owner }}
- name: Build regular image
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
targets: ci
push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
files: |
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
cwd://${{ steps.meta.outputs.bake-file }}
# https://github.com/docker/buildx/issues/2105
- name: Create manifest

1
.gitignore vendored
View File

@@ -91,3 +91,4 @@ ENV/
# Rope project settings
.ropeproject
.secrets

View File

@@ -1,6 +1,6 @@
ARG WINDOWS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltcs2022
FROM --platform=$BUILDPLATFORM golang:1.23 as builder
FROM --platform=$BUILDPLATFORM golang:1.25 as builder
ARG TARGETARCH
ARG TARGETOS

View File

@@ -1,5 +1,5 @@
name ?= goldpinger
version ?= v3.10.2
version ?= v3.11.0
bin ?= goldpinger
pkg ?= "github.com/bloomberg/goldpinger"
tag = $(name):$(version)

View File

@@ -1,6 +1,7 @@
# Goldpinger
[![Publish](https://github.com/bloomberg/goldpinger/actions/workflows/publish.yml/badge.svg)](https://github.com/bloomberg/goldpinger/actions/workflows/publish.yml)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bloomberg/goldpinger)
__Goldpinger__ makes calls between its instances to monitor your networking.
It runs as a [`DaemonSet`](#example-yaml) on `Kubernetes` and produces `Prometheus` metrics that can be [scraped](#prometheus), [visualised](#grafana) and [alerted](#alert-manager) on.
@@ -24,6 +25,7 @@ Oh, and it gives you the graph below for your cluster. Check out the [video expl
- [Authentication with Kubernetes API](#authentication-with-kubernetes-api)
- [Example YAML](#example-yaml)
- [Note on DNS](#note-on-dns)
- [UDP probe for packet loss, hop count, and RTT](#udp-probe-for-packet-loss-hop-count-and-rtt)
- [Usage](#usage)
- [UI](#ui)
- [API](#api)
@@ -286,10 +288,56 @@ Instances can also be configured to do simple TCP or HTTP checks on external tar
value: 10.34.5.141:5000 10.34.195.193:6442
```
the timeouts for the TCP, DNS and HTTP checks can be configured via `TCP_TARGETS_TIMEOUT`, `DNS_TARGETS_TIMEOUT` and `HTTP_TARGETS_TIMEOUT` respectively.
the timeouts for the TCP, DNS and HTTP checks can be configured via `TCP_TARGETS_TIMEOUT`, `DNS_TARGETS_TIMEOUT` and `HTTP_TARGETS_TIMEOUT` respectively.
![screenshot-tcp-http-checks](./extras/tcp-checks-screenshot.png)
### UDP probe for packet loss, hop count, and RTT
In natively routed Kubernetes environments (e.g. Cilium, Calico in BGP mode), the existing HTTP ping can mask network issues: TCP retransmits hide packet loss, and HTTP latency includes the 3-way handshake, TLS, and application overhead. The UDP probe gives you visibility into the actual network layer.
When enabled, each goldpinger pod runs a UDP echo listener. During each ping cycle, the prober sends a configurable number of sequenced UDP packets to each peer; the peer echoes them back. From the replies, goldpinger computes:
- **Packet loss** — percentage of packets that were not returned, surfacing degraded links before they impact applications
- **Hop count** — estimated from the IPv4 TTL or IPv6 HopLimit on received replies, useful for detecting asymmetric routing or unexpected topology changes
- **UDP RTT** — average round-trip time with sub-millisecond precision, isolating network latency from TCP/HTTP overhead
The feature is disabled by default and can be enabled with the following environment variables:
```sh
UDP_ENABLED=true # enable UDP probing and echo listener
UDP_PORT=6969 # listener port (default: 6969)
UDP_PACKET_COUNT=10 # packets per probe (default: 10)
UDP_PACKET_SIZE=64 # bytes per packet (default: 64)
UDP_TIMEOUT=1s # probe timeout (default: 1s)
```
Or via the Helm chart:
```yaml
goldpinger:
udp:
enabled: true
port: 6969
```
This adds three Prometheus metrics:
```sh
goldpinger_peers_loss_pct # gauge: UDP packet loss percentage (0-100)
goldpinger_peers_hop_count # gauge: estimated hop count
goldpinger_peers_udp_rtt_s # histogram: UDP round-trip time in seconds
goldpinger_udp_errors_total # counter: UDP probe errors
```
Links with partial loss are shown as yellow edges in the graph UI, and edge labels display the UDP RTT instead of HTTP latency when available.
![screenshot-udp-yellow-edges](./extras/udp-yellow-edges.png)
![screenshot-udp-grafana](./extras/udp-grafana-dashboards.png)
No new dependencies are required (`golang.org/x/net` is already in go.mod), and no additional container capabilities are needed.
## Usage
### UI
@@ -316,10 +364,12 @@ These are probably the droids you are looking for:
```sh
goldpinger_peers_response_time_s_*
goldpinger_peers_response_time_s_*
goldpinger_nodes_health_total
goldpinger_stats_total
goldpinger_errors_total
goldpinger_peers_loss_pct # (UDP probe, when enabled)
goldpinger_peers_hop_count # (UDP probe, when enabled)
goldpinger_peers_udp_rtt_s_* # (UDP probe, when enabled)
```
### Grafana

View File

@@ -1,7 +1,7 @@
apiVersion: v1
name: goldpinger
appVersion: "3.10.2"
version: 1.0.1
appVersion: "3.11.0"
version: 1.1.0
description: Goldpinger is a tool to help debug, troubleshoot and visualize network connectivity and slowness issues.
home: https://github.com/bloomberg/goldpinger
sources:

View File

@@ -50,6 +50,12 @@ spec:
value: "{{ .Values.goldpinger.port }}"
- name: LABEL_SELECTOR
value: "app.kubernetes.io/name={{ include "goldpinger.name" . }}"
{{- if .Values.goldpinger.udp.enabled }}
- name: UDP_ENABLED
value: "true"
- name: UDP_PORT
value: "{{ .Values.goldpinger.udp.port }}"
{{- end }}
{{- if .Values.extraEnv -}}
{{ toYaml .Values.extraEnv | nindent 12 }}
{{- end }}
@@ -66,6 +72,11 @@ spec:
hostPort: {{ $.Values.goldpinger.port }}
{{- end }}
{{- end }}
{{- if .Values.goldpinger.udp.enabled }}
- name: udp-probe
containerPort: {{ .Values.goldpinger.udp.port }}
protocol: UDP
{{- end }}
livenessProbe:
httpGet:
path: /

View File

@@ -18,6 +18,12 @@ spec:
targetPort: {{ .Values.goldpinger.port }}
protocol: TCP
name: http
{{- if .Values.goldpinger.udp.enabled }}
- port: {{ .Values.goldpinger.udp.port }}
targetPort: {{ .Values.goldpinger.udp.port }}
protocol: UDP
name: udp-probe
{{- end }}
selector:
{{- include "goldpinger.selectorLabels" . | nindent 4 }}
{{- if .Values.service.loadBalancerSourceRanges }}

View File

@@ -23,6 +23,9 @@ serviceAccount:
goldpinger:
port: 8080
udp:
enabled: false
port: 6969
zapConfig: |
{
"level": "info",

View File

@@ -186,6 +186,10 @@ func main() {
server.ConfigureAPI()
goldpinger.StartUpdater()
if goldpinger.GoldpingerConfig.UDPEnabled {
go goldpinger.StartUDPListener(goldpinger.GoldpingerConfig.UDPPort)
}
logger.Info("All good, starting serving the API")
// serve API

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

BIN
extras/udp-yellow-edges.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

110
go.mod
View File

@@ -1,26 +1,26 @@
module github.com/bloomberg/goldpinger/v3
go 1.23
go 1.25.0
require (
github.com/cespare/xxhash v1.1.0
github.com/go-openapi/errors v0.22.0
github.com/go-openapi/loads v0.22.0
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/spec v0.21.0
github.com/go-openapi/strfmt v0.23.0
github.com/go-openapi/swag v0.23.0
github.com/go-openapi/validate v0.24.0
github.com/jessevdk/go-flags v1.5.0
github.com/prometheus/client_golang v1.19.0
github.com/go-openapi/errors v0.22.6
github.com/go-openapi/loads v0.23.2
github.com/go-openapi/runtime v0.29.2
github.com/go-openapi/spec v0.22.3
github.com/go-openapi/strfmt v0.25.0
github.com/go-openapi/swag v0.25.4
github.com/go-openapi/validate v0.25.1
github.com/jessevdk/go-flags v1.6.1
github.com/prometheus/client_golang v1.23.2
github.com/stuartnelson3/go-rendezvous v0.2.0
go.uber.org/zap v1.27.0
golang.org/x/image v0.21.0
golang.org/x/net v0.24.0
k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.3
k8s.io/client-go v0.29.3
k8s.io/utils v0.0.0-20240310230437-4693a0247e57
go.uber.org/zap v1.27.1
golang.org/x/image v0.35.0
golang.org/x/net v0.49.0
k8s.io/api v0.35.0
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/utils v0.0.0-20260108192941-914a6e750570
)
require (
@@ -29,49 +29,69 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/analysis v0.24.2 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/mangling v0.25.4 // indirect
github.com/go-openapi/swag/netutils v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.52.3 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect
go.opentelemetry.io/otel v1.25.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.17.7 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

130
go.sum
View File

@@ -15,39 +15,91 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/analysis v0.24.2 h1:6p7WXEuKy1llDgOH8FooVeO+Uq2za9qoAOq4ZN08B50=
github.com/go-openapi/analysis v0.24.2/go.mod h1:x27OOHKANE0lutg2ml4kzYLoHGMKgRm1Cj2ijVOjJuE=
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo=
github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=
github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0=
github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -62,6 +114,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -74,6 +128,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -81,12 +137,15 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
@@ -95,47 +154,77 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stuartnelson3/go-rendezvous v0.2.0 h1:H5IexrsptBzCMQEjTRrNH20MVXGqpFf1JUCPglaxd6I=
github.com/stuartnelson3/go-rendezvous v0.2.0/go.mod h1:njfgP6zISyRnZ3iQN13NSEILfSNLN4ysxBoGxHs5PJ0=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU=
go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -144,27 +233,41 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -177,9 +280,13 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -189,19 +296,42 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA=
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -2,14 +2,12 @@
package client
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/bloomberg/goldpinger/v3/pkg/client/operations"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/bloomberg/goldpinger/v3/pkg/client/operations"
)
// Default goldpinger HTTP client.

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewCheckAllPodsParamsWithHTTPClient(client *http.Client) *CheckAllPodsParam
}
}
/* CheckAllPodsParams contains all the parameters to send to the API endpoint
for the check all pods operation.
/*
CheckAllPodsParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the check all pods operation.
Typically these are written to a http.Request.
*/
type CheckAllPodsParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type CheckAllPodsReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewCheckAllPodsOK()
@@ -30,7 +29,7 @@ func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consu
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /check_all] checkAllPods", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewCheckAllPodsOK() *CheckAllPodsOK {
return &CheckAllPodsOK{}
}
/* CheckAllPodsOK describes a response with status code 200, with default header values.
/*
CheckAllPodsOK describes a response with status code 200, with default header values.
Success, return response
*/
@@ -47,9 +47,46 @@ type CheckAllPodsOK struct {
Payload *models.CheckAllResults
}
func (o *CheckAllPodsOK) Error() string {
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %+v", 200, o.Payload)
// IsSuccess returns true when this check all pods o k response has a 2xx status code
func (o *CheckAllPodsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this check all pods o k response has a 3xx status code
func (o *CheckAllPodsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this check all pods o k response has a 4xx status code
func (o *CheckAllPodsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this check all pods o k response has a 5xx status code
func (o *CheckAllPodsOK) IsServerError() bool {
return false
}
// IsCode returns true when this check all pods o k response a status code equal to that given
func (o *CheckAllPodsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the check all pods o k response
func (o *CheckAllPodsOK) Code() int {
return 200
}
func (o *CheckAllPodsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %s", 200, payload)
}
func (o *CheckAllPodsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %s", 200, payload)
}
func (o *CheckAllPodsOK) GetPayload() *models.CheckAllResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *CheckAllPodsOK) readResponse(response runtime.ClientResponse, consumer
o.Payload = new(models.CheckAllResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewCheckServicePodsParamsWithHTTPClient(client *http.Client) *CheckServiceP
}
}
/* CheckServicePodsParams contains all the parameters to send to the API endpoint
for the check service pods operation.
/*
CheckServicePodsParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the check service pods operation.
Typically these are written to a http.Request.
*/
type CheckServicePodsParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type CheckServicePodsReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewCheckServicePodsOK()
@@ -30,7 +29,7 @@ func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, c
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /check] checkServicePods", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewCheckServicePodsOK() *CheckServicePodsOK {
return &CheckServicePodsOK{}
}
/* CheckServicePodsOK describes a response with status code 200, with default header values.
/*
CheckServicePodsOK describes a response with status code 200, with default header values.
Success, return response
*/
@@ -47,9 +47,46 @@ type CheckServicePodsOK struct {
Payload *models.CheckResults
}
func (o *CheckServicePodsOK) Error() string {
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %+v", 200, o.Payload)
// IsSuccess returns true when this check service pods o k response has a 2xx status code
func (o *CheckServicePodsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this check service pods o k response has a 3xx status code
func (o *CheckServicePodsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this check service pods o k response has a 4xx status code
func (o *CheckServicePodsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this check service pods o k response has a 5xx status code
func (o *CheckServicePodsOK) IsServerError() bool {
return false
}
// IsCode returns true when this check service pods o k response a status code equal to that given
func (o *CheckServicePodsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the check service pods o k response
func (o *CheckServicePodsOK) Code() int {
return 200
}
func (o *CheckServicePodsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %s", 200, payload)
}
func (o *CheckServicePodsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %s", 200, payload)
}
func (o *CheckServicePodsOK) GetPayload() *models.CheckResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *CheckServicePodsOK) readResponse(response runtime.ClientResponse, consu
o.Payload = new(models.CheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewClusterHealthParamsWithHTTPClient(client *http.Client) *ClusterHealthPar
}
}
/* ClusterHealthParams contains all the parameters to send to the API endpoint
for the cluster health operation.
/*
ClusterHealthParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the cluster health operation.
Typically these are written to a http.Request.
*/
type ClusterHealthParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type ClusterHealthReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewClusterHealthOK()
@@ -36,7 +35,7 @@ func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, cons
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /cluster_health] clusterHealth", response, response.Code())
}
}
@@ -45,7 +44,8 @@ func NewClusterHealthOK() *ClusterHealthOK {
return &ClusterHealthOK{}
}
/* ClusterHealthOK describes a response with status code 200, with default header values.
/*
ClusterHealthOK describes a response with status code 200, with default header values.
Healthy cluster
*/
@@ -53,9 +53,46 @@ type ClusterHealthOK struct {
Payload *models.ClusterHealthResults
}
func (o *ClusterHealthOK) Error() string {
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %+v", 200, o.Payload)
// IsSuccess returns true when this cluster health o k response has a 2xx status code
func (o *ClusterHealthOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this cluster health o k response has a 3xx status code
func (o *ClusterHealthOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this cluster health o k response has a 4xx status code
func (o *ClusterHealthOK) IsClientError() bool {
return false
}
// IsServerError returns true when this cluster health o k response has a 5xx status code
func (o *ClusterHealthOK) IsServerError() bool {
return false
}
// IsCode returns true when this cluster health o k response a status code equal to that given
func (o *ClusterHealthOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the cluster health o k response
func (o *ClusterHealthOK) Code() int {
return 200
}
func (o *ClusterHealthOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %s", 200, payload)
}
func (o *ClusterHealthOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %s", 200, payload)
}
func (o *ClusterHealthOK) GetPayload() *models.ClusterHealthResults {
return o.Payload
}
@@ -65,7 +102,7 @@ func (o *ClusterHealthOK) readResponse(response runtime.ClientResponse, consumer
o.Payload = new(models.ClusterHealthResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}
@@ -77,7 +114,8 @@ func NewClusterHealthIMATeapot() *ClusterHealthIMATeapot {
return &ClusterHealthIMATeapot{}
}
/* ClusterHealthIMATeapot describes a response with status code 418, with default header values.
/*
ClusterHealthIMATeapot describes a response with status code 418, with default header values.
Unhealthy cluster
*/
@@ -85,9 +123,46 @@ type ClusterHealthIMATeapot struct {
Payload *models.ClusterHealthResults
}
func (o *ClusterHealthIMATeapot) Error() string {
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %+v", 418, o.Payload)
// IsSuccess returns true when this cluster health i m a teapot response has a 2xx status code
func (o *ClusterHealthIMATeapot) IsSuccess() bool {
return false
}
// IsRedirect returns true when this cluster health i m a teapot response has a 3xx status code
func (o *ClusterHealthIMATeapot) IsRedirect() bool {
return false
}
// IsClientError returns true when this cluster health i m a teapot response has a 4xx status code
func (o *ClusterHealthIMATeapot) IsClientError() bool {
return true
}
// IsServerError returns true when this cluster health i m a teapot response has a 5xx status code
func (o *ClusterHealthIMATeapot) IsServerError() bool {
return false
}
// IsCode returns true when this cluster health i m a teapot response a status code equal to that given
func (o *ClusterHealthIMATeapot) IsCode(code int) bool {
return code == 418
}
// Code gets the status code for the cluster health i m a teapot response
func (o *ClusterHealthIMATeapot) Code() int {
return 418
}
func (o *ClusterHealthIMATeapot) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %s", 418, payload)
}
func (o *ClusterHealthIMATeapot) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %s", 418, payload)
}
func (o *ClusterHealthIMATeapot) GetPayload() *models.ClusterHealthResults {
return o.Payload
}
@@ -97,7 +172,7 @@ func (o *ClusterHealthIMATeapot) readResponse(response runtime.ClientResponse, c
o.Payload = new(models.ClusterHealthResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewHealthzParamsWithHTTPClient(client *http.Client) *HealthzParams {
}
}
/* HealthzParams contains all the parameters to send to the API endpoint
for the healthz operation.
/*
HealthzParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the healthz operation.
Typically these are written to a http.Request.
*/
type HealthzParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type HealthzReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewHealthzOK()
@@ -36,7 +35,7 @@ func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer r
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /healthz] healthz", response, response.Code())
}
}
@@ -45,7 +44,8 @@ func NewHealthzOK() *HealthzOK {
return &HealthzOK{}
}
/* HealthzOK describes a response with status code 200, with default header values.
/*
HealthzOK describes a response with status code 200, with default header values.
Health check report
*/
@@ -53,9 +53,46 @@ type HealthzOK struct {
Payload *models.HealthCheckResults
}
func (o *HealthzOK) Error() string {
return fmt.Sprintf("[GET /healthz][%d] healthzOK %+v", 200, o.Payload)
// IsSuccess returns true when this healthz o k response has a 2xx status code
func (o *HealthzOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this healthz o k response has a 3xx status code
func (o *HealthzOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this healthz o k response has a 4xx status code
func (o *HealthzOK) IsClientError() bool {
return false
}
// IsServerError returns true when this healthz o k response has a 5xx status code
func (o *HealthzOK) IsServerError() bool {
return false
}
// IsCode returns true when this healthz o k response a status code equal to that given
func (o *HealthzOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the healthz o k response
func (o *HealthzOK) Code() int {
return 200
}
func (o *HealthzOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzOK %s", 200, payload)
}
func (o *HealthzOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzOK %s", 200, payload)
}
func (o *HealthzOK) GetPayload() *models.HealthCheckResults {
return o.Payload
}
@@ -65,7 +102,7 @@ func (o *HealthzOK) readResponse(response runtime.ClientResponse, consumer runti
o.Payload = new(models.HealthCheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}
@@ -77,7 +114,8 @@ func NewHealthzServiceUnavailable() *HealthzServiceUnavailable {
return &HealthzServiceUnavailable{}
}
/* HealthzServiceUnavailable describes a response with status code 503, with default header values.
/*
HealthzServiceUnavailable describes a response with status code 503, with default header values.
Unhealthy service
*/
@@ -85,9 +123,46 @@ type HealthzServiceUnavailable struct {
Payload *models.HealthCheckResults
}
func (o *HealthzServiceUnavailable) Error() string {
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %+v", 503, o.Payload)
// IsSuccess returns true when this healthz service unavailable response has a 2xx status code
func (o *HealthzServiceUnavailable) IsSuccess() bool {
return false
}
// IsRedirect returns true when this healthz service unavailable response has a 3xx status code
func (o *HealthzServiceUnavailable) IsRedirect() bool {
return false
}
// IsClientError returns true when this healthz service unavailable response has a 4xx status code
func (o *HealthzServiceUnavailable) IsClientError() bool {
return false
}
// IsServerError returns true when this healthz service unavailable response has a 5xx status code
func (o *HealthzServiceUnavailable) IsServerError() bool {
return true
}
// IsCode returns true when this healthz service unavailable response a status code equal to that given
func (o *HealthzServiceUnavailable) IsCode(code int) bool {
return code == 503
}
// Code gets the status code for the healthz service unavailable response
func (o *HealthzServiceUnavailable) Code() int {
return 503
}
func (o *HealthzServiceUnavailable) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %s", 503, payload)
}
func (o *HealthzServiceUnavailable) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %s", 503, payload)
}
func (o *HealthzServiceUnavailable) GetPayload() *models.HealthCheckResults {
return o.Payload
}
@@ -97,7 +172,7 @@ func (o *HealthzServiceUnavailable) readResponse(response runtime.ClientResponse
o.Payload = new(models.HealthCheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,13 +2,11 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
@@ -17,6 +15,31 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientServi
return &Client{transport: transport, formats: formats}
}
// New creates a new operations API client with basic auth credentials.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - user: user for basic authentication header.
// - password: password for basic authentication header.
func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
return &Client{transport: transport, formats: strfmt.Default}
}
// New creates a new operations API client with a bearer token for authentication.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - bearerToken: bearer token for Bearer authentication header.
func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BearerToken(bearerToken)
return &Client{transport: transport, formats: strfmt.Default}
}
/*
Client for operations API
*/
@@ -25,7 +48,7 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
// ClientOption may be used to customize the behavior of Client methods.
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
@@ -44,10 +67,10 @@ type ClientService interface {
}
/*
CheckAllPods Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
CheckAllPods Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
*/
func (a *Client) CheckAllPods(params *CheckAllPodsParams, opts ...ClientOption) (*CheckAllPodsOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewCheckAllPodsParams()
}
@@ -66,26 +89,31 @@ func (a *Client) CheckAllPods(params *CheckAllPodsParams, opts ...ClientOption)
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*CheckAllPodsOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for checkAllPods: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
CheckServicePods Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
CheckServicePods Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
*/
func (a *Client) CheckServicePods(params *CheckServicePodsParams, opts ...ClientOption) (*CheckServicePodsOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewCheckServicePodsParams()
}
@@ -104,26 +132,31 @@ func (a *Client) CheckServicePods(params *CheckServicePodsParams, opts ...Client
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*CheckServicePodsOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for checkServicePods: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
ClusterHealth Checks the full graph. Returns a binary OK or not OK.
ClusterHealth Checks the full graph. Returns a binary OK or not OK.
*/
func (a *Client) ClusterHealth(params *ClusterHealthParams, opts ...ClientOption) (*ClusterHealthOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewClusterHealthParams()
}
@@ -142,26 +175,31 @@ func (a *Client) ClusterHealth(params *ClusterHealthParams, opts ...ClientOption
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*ClusterHealthOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for clusterHealth: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
Healthz The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
Healthz The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
*/
func (a *Client) Healthz(params *HealthzParams, opts ...ClientOption) (*HealthzOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewHealthzParams()
}
@@ -180,26 +218,31 @@ func (a *Client) Healthz(params *HealthzParams, opts ...ClientOption) (*HealthzO
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*HealthzOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for healthz: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
Ping return query stats
Ping return query stats
*/
func (a *Client) Ping(params *PingParams, opts ...ClientOption) (*PingOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewPingParams()
}
@@ -218,17 +261,22 @@ func (a *Client) Ping(params *PingParams, opts ...ClientOption) (*PingOK, error)
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*PingOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for ping: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewPingParamsWithHTTPClient(client *http.Client) *PingParams {
}
}
/* PingParams contains all the parameters to send to the API endpoint
for the ping operation.
/*
PingParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the ping operation.
Typically these are written to a http.Request.
*/
type PingParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type PingReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewPingOK()
@@ -30,7 +29,7 @@ func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runt
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /ping] ping", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewPingOK() *PingOK {
return &PingOK{}
}
/* PingOK describes a response with status code 200, with default header values.
/*
PingOK describes a response with status code 200, with default header values.
return success
*/
@@ -47,9 +47,46 @@ type PingOK struct {
Payload *models.PingResults
}
func (o *PingOK) Error() string {
return fmt.Sprintf("[GET /ping][%d] pingOK %+v", 200, o.Payload)
// IsSuccess returns true when this ping o k response has a 2xx status code
func (o *PingOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ping o k response has a 3xx status code
func (o *PingOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ping o k response has a 4xx status code
func (o *PingOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ping o k response has a 5xx status code
func (o *PingOK) IsServerError() bool {
return false
}
// IsCode returns true when this ping o k response a status code equal to that given
func (o *PingOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ping o k response
func (o *PingOK) Code() int {
return 200
}
func (o *PingOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /ping][%d] pingOK %s", 200, payload)
}
func (o *PingOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /ping][%d] pingOK %s", 200, payload)
}
func (o *PingOK) GetPayload() *models.PingResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *PingOK) readResponse(response runtime.ClientResponse, consumer runtime.
o.Payload = new(models.PingResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -44,6 +44,13 @@ var GoldpingerConfig = struct {
IPVersions []string `long:"ip-versions" description:"The IP versions to use (space delimited). Possible values are 4 and 6 (defaults to 4)." env:"IP_VERSIONS" env-delim:" "`
// UDP probe settings
UDPEnabled bool `long:"udp-enabled" description:"Enable UDP probe for loss and hop metrics" env:"UDP_ENABLED"`
UDPPort int `long:"udp-port" description:"Port for UDP echo listener" env:"UDP_PORT" default:"6969"`
UDPPacketCount int `long:"udp-packet-count" description:"Number of UDP packets to send per probe" env:"UDP_PACKET_COUNT" default:"10"`
UDPPacketSize int `long:"udp-packet-size" description:"Size of each UDP probe packet in bytes" env:"UDP_PACKET_SIZE" default:"64"`
UDPTimeout time.Duration `long:"udp-timeout" description:"Timeout for UDP probe" env:"UDP_TIMEOUT" default:"1s"`
// Timeouts
PingTimeoutMs int64 `long:"ping-timeout-ms" description:"The timeout in milliseconds for a ping call to other goldpinger pods(deprecated)" env:"PING_TIMEOUT_MS" default:"300"`
CheckTimeoutMs int64 `long:"check-timeout-ms" description:"The timeout in milliseconds for a check call to other goldpinger pods(deprecated)" env:"CHECK_TIMEOUT_MS" default:"1000"`

View File

@@ -16,6 +16,7 @@ package goldpinger
import (
"context"
"sync"
"time"
"go.uber.org/zap"
@@ -111,6 +112,24 @@ func (p *Pinger) Ping() {
CountCall("made", "ping")
start := time.Now()
// Run HTTP ping and UDP probe concurrently
var udpResult UDPProbeResult
var wg sync.WaitGroup
if GoldpingerConfig.UDPEnabled {
wg.Add(1)
go func() {
defer wg.Done()
targetIP := pickPodHostIP(p.pod.PodIP, p.pod.HostIP)
udpResult = ProbeUDP(
targetIP,
GoldpingerConfig.UDPPort,
GoldpingerConfig.UDPPacketCount,
GoldpingerConfig.UDPPacketSize,
GoldpingerConfig.UDPTimeout,
)
}()
}
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
@@ -120,7 +139,34 @@ func (p *Pinger) Ping() {
responseTimeMs := responseTime.Nanoseconds() / int64(time.Millisecond)
p.histogram.Observe(responseTime.Seconds())
// Wait for UDP probe to complete
wg.Wait()
OK := (err == nil)
var lossPct float64
var hopCount int32
var udpRttMs float64
if GoldpingerConfig.UDPEnabled {
lossPct = udpResult.LossPct
hopCount = udpResult.HopCount
udpRttMs = udpResult.AvgRttS * 1000.0
if udpResult.Err != nil {
p.logger.Warn("UDP probe error", zap.Error(udpResult.Err))
} else {
p.logger.Debug("UDP probe complete",
zap.Float64("lossPct", lossPct),
zap.Int32("hopCount", hopCount),
zap.Float64("udpRttMs", udpRttMs),
)
}
SetPeerLossPct(p.pod.HostIP, p.pod.PodIP, lossPct)
SetPeerHopCount(p.pod.HostIP, p.pod.PodIP, hopCount)
if udpResult.AvgRttS > 0 {
ObservePeerUDPRtt(p.pod.HostIP, p.pod.PodIP, udpResult.AvgRttS)
}
}
if OK {
p.resultsChan <- PingAllPodsResult{
podName: p.pod.Name,
@@ -132,6 +178,9 @@ func (p *Pinger) Ping() {
Response: resp.Payload,
StatusCode: 200,
ResponseTimeMs: responseTimeMs,
LossPct: lossPct,
HopCount: hopCount,
UDPRttMs: udpRttMs,
},
}
p.logger.Debug("Success pinging pod", zap.Duration("responseTime", responseTime))
@@ -146,6 +195,9 @@ func (p *Pinger) Ping() {
Error: err.Error(),
StatusCode: 504,
ResponseTimeMs: responseTimeMs,
LossPct: lossPct,
HopCount: hopCount,
UDPRttMs: udpRttMs,
},
}
p.logger.Warn("Ping returned error", zap.Duration("responseTime", responseTime), zap.Error(err))

View File

@@ -123,6 +123,50 @@ var (
"host",
},
)
goldpingerPeersLossPct = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "goldpinger_peers_loss_pct",
Help: "UDP packet loss percentage to peer (0-100)",
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerPeersHopCount = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "goldpinger_peers_hop_count",
Help: "Estimated network hop count to peer from UDP TTL",
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerPeersUDPRtt = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "goldpinger_peers_udp_rtt_s",
Help: "Histogram of UDP round-trip times to peers in seconds",
Buckets: []float64{.0001, .00025, .0005, .001, .0025, .005, .01, .025, .05, .1, .25, .5, 1},
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerUDPErrorsCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "goldpinger_udp_errors_total",
Help: "Statistics of UDP probe errors per instance",
},
[]string{
"goldpinger_instance",
"host",
},
)
bootTime = time.Now()
)
@@ -136,6 +180,10 @@ func init() {
prometheus.MustRegister(goldpingerDnsErrorsCounter)
prometheus.MustRegister(goldPingerHttpErrorsCounter)
prometheus.MustRegister(goldPingerTcpErrorsCounter)
prometheus.MustRegister(goldpingerPeersLossPct)
prometheus.MustRegister(goldpingerPeersHopCount)
prometheus.MustRegister(goldpingerPeersUDPRtt)
prometheus.MustRegister(goldpingerUDPErrorsCounter)
zap.L().Info("Metrics setup - see /metrics")
}
@@ -210,6 +258,48 @@ func CountHttpError(host string) {
).Inc()
}
// SetPeerLossPct sets the UDP packet loss percentage gauge for a peer
func SetPeerLossPct(hostIP, podIP string, lossPct float64) {
goldpingerPeersLossPct.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Set(lossPct)
}
// SetPeerHopCount sets the estimated hop count gauge for a peer
func SetPeerHopCount(hostIP, podIP string, hopCount int32) {
goldpingerPeersHopCount.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Set(float64(hopCount))
}
// DeletePeerUDPMetrics removes stale UDP metric labels for a destroyed peer
func DeletePeerUDPMetrics(hostIP, podIP string) {
goldpingerPeersLossPct.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
goldpingerPeersHopCount.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
goldpingerPeersUDPRtt.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
}
// ObservePeerUDPRtt records a UDP RTT observation in seconds
func ObservePeerUDPRtt(hostIP, podIP string, rttS float64) {
goldpingerPeersUDPRtt.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Observe(rttS)
}
// CountUDPError counts instances of UDP probe errors
func CountUDPError(host string) {
goldpingerUDPErrorsCounter.WithLabelValues(
GoldpingerConfig.Hostname,
host,
).Inc()
}
// returns a timer for easy observing of the durations of calls to kubernetes API
func GetLabeledKubernetesCallsTimer() *prometheus.Timer {
return prometheus.NewTimer(

225
pkg/goldpinger/udp_probe.go Normal file
View File

@@ -0,0 +1,225 @@
// Copyright 2018 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package goldpinger
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"time"
"go.uber.org/zap"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
udpMagic = 0x47504E47 // "GPNG"
udpHeaderSize = 16 // 4 magic + 4 seq + 8 timestamp
// udpMaxPacketSize is the maximum UDP packet we will read. Sized to the
// standard Ethernet MTU (1500 bytes) which is the largest packet that
// will survive most networks without fragmentation. Any configured
// UDP_PACKET_SIZE larger than this will be clamped to this value to
// avoid silent truncation on the receive side.
udpMaxPacketSize = 1500
)
// UDPProbeResult holds the results of a UDP probe to a peer
type UDPProbeResult struct {
LossPct float64
HopCount int32
AvgRttS float64
Err error
}
// StartUDPListener starts a UDP echo listener on the given port.
// It echoes back any packet that starts with the GPNG magic number.
func StartUDPListener(port int) {
addr := net.JoinHostPort("::", strconv.Itoa(port))
pc, err := net.ListenPacket("udp", addr)
if err != nil {
zap.L().Fatal("Failed to start UDP listener", zap.String("addr", addr), zap.Error(err))
}
defer pc.Close()
zap.L().Info("UDP echo listener started", zap.String("addr", addr))
buf := make([]byte, udpMaxPacketSize)
for {
n, remoteAddr, err := pc.ReadFrom(buf)
if err != nil {
zap.L().Warn("UDP read error", zap.Error(err))
continue
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(buf[0:4])
if magic != udpMagic {
continue
}
// Echo back the packet as-is
_, err = pc.WriteTo(buf[:n], remoteAddr)
if err != nil {
zap.L().Warn("UDP write error", zap.Error(err))
}
}
}
// ProbeUDP sends count UDP packets to the target and measures loss and hop count.
func ProbeUDP(targetIP string, port, count, size int, timeout time.Duration) UDPProbeResult {
if count <= 0 {
return UDPProbeResult{Err: fmt.Errorf("packet count must be > 0, got %d", count)}
}
if size < udpHeaderSize {
size = udpHeaderSize
}
if size > udpMaxPacketSize {
size = udpMaxPacketSize
}
addr := net.JoinHostPort(targetIP, strconv.Itoa(port))
conn, err := net.Dial("udp", addr)
if err != nil {
CountUDPError(targetIP)
return UDPProbeResult{LossPct: 100, Err: fmt.Errorf("dial: %w", err)}
}
defer conn.Close()
// Determine if this is IPv4 or IPv6 and set up TTL/HopLimit reading
isIPv6 := net.ParseIP(targetIP).To4() == nil
var ttlValue int
ttlFound := false
if isIPv6 {
p := ipv6.NewPacketConn(conn.(*net.UDPConn))
if err := p.SetControlMessage(ipv6.FlagHopLimit, true); err != nil {
zap.L().Debug("Cannot set IPv6 hop limit flag", zap.Error(err))
}
} else {
p := ipv4.NewPacketConn(conn.(*net.UDPConn))
if err := p.SetControlMessage(ipv4.FlagTTL, true); err != nil {
zap.L().Debug("Cannot set IPv4 TTL flag", zap.Error(err))
}
}
// Build packet
pkt := make([]byte, size)
binary.BigEndian.PutUint32(pkt[0:4], udpMagic)
// Send all packets
for i := 0; i < count; i++ {
binary.BigEndian.PutUint32(pkt[4:8], uint32(i))
binary.BigEndian.PutUint64(pkt[8:16], uint64(time.Now().UnixNano()))
_, err := conn.Write(pkt)
if err != nil {
CountUDPError(targetIP)
zap.L().Debug("UDP send error", zap.Int("seq", i), zap.Error(err))
}
}
// Receive replies
received := 0
var totalRttNs int64
deadline := time.Now().Add(timeout)
conn.SetReadDeadline(deadline)
recvBuf := make([]byte, udpMaxPacketSize)
if isIPv6 {
p := ipv6.NewPacketConn(conn.(*net.UDPConn))
for received < count {
n, cm, _, err := p.ReadFrom(recvBuf)
now := time.Now()
if err != nil {
break
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(recvBuf[0:4])
if magic != udpMagic {
continue
}
sentNs := int64(binary.BigEndian.Uint64(recvBuf[8:16]))
totalRttNs += now.UnixNano() - sentNs
received++
if cm != nil && cm.HopLimit > 0 && !ttlFound {
ttlValue = cm.HopLimit
ttlFound = true
}
}
} else {
p := ipv4.NewPacketConn(conn.(*net.UDPConn))
for received < count {
n, cm, _, err := p.ReadFrom(recvBuf)
now := time.Now()
if err != nil {
break
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(recvBuf[0:4])
if magic != udpMagic {
continue
}
sentNs := int64(binary.BigEndian.Uint64(recvBuf[8:16]))
totalRttNs += now.UnixNano() - sentNs
received++
if cm != nil && cm.TTL > 0 && !ttlFound {
ttlValue = cm.TTL
ttlFound = true
}
}
}
lossPct := float64(count-received) / float64(count) * 100.0
var hopCount int32
if ttlFound {
hopCount = estimateHops(ttlValue)
}
var avgRttS float64
if received > 0 {
avgRttS = float64(totalRttNs) / float64(received) / 1e9
}
return UDPProbeResult{
LossPct: lossPct,
HopCount: hopCount,
AvgRttS: avgRttS,
}
}
// estimateHops estimates the number of hops from the received TTL.
// Common initial TTL values are 64 (Linux) and 128 (Windows).
func estimateHops(receivedTTL int) int32 {
if receivedTTL <= 0 {
return 0
}
initialTTL := 64
if receivedTTL > 64 {
initialTTL = 128
}
hops := initialTTL - receivedTTL
if hops < 0 {
hops = 0
}
return int32(hops)
}

View File

@@ -0,0 +1,180 @@
package goldpinger
import (
"encoding/binary"
"net"
"sync/atomic"
"testing"
"time"
)
func startTestEchoListener(t *testing.T) (int, func()) {
t.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
go func() {
buf := make([]byte, udpMaxPacketSize)
for {
n, addr, err := pc.ReadFrom(buf)
if err != nil {
return
}
if n >= udpHeaderSize {
magic := binary.BigEndian.Uint32(buf[0:4])
if magic == udpMagic {
pc.WriteTo(buf[:n], addr)
}
}
}
}()
return port, func() { pc.Close() }
}
// startLossyEchoListener echoes back packets but drops every dropEveryN-th
// packet (1-indexed). For example, dropEveryN=3 drops packets 3, 6, 9, etc.
func startLossyEchoListener(t *testing.T, dropEveryN int) (int, func()) {
t.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
var counter atomic.Int64
go func() {
buf := make([]byte, udpMaxPacketSize)
for {
n, addr, err := pc.ReadFrom(buf)
if err != nil {
return
}
if n >= udpHeaderSize {
magic := binary.BigEndian.Uint32(buf[0:4])
if magic == udpMagic {
seq := counter.Add(1)
if seq%int64(dropEveryN) == 0 {
// Drop this packet — don't echo
continue
}
pc.WriteTo(buf[:n], addr)
}
}
}
}()
return port, func() { pc.Close() }
}
func TestProbeUDP_NoLoss(t *testing.T) {
port, cleanup := startTestEchoListener(t)
defer cleanup()
result := ProbeUDP("127.0.0.1", port, 10, 64, 2*time.Second)
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
if result.LossPct != 0 {
t.Errorf("expected 0%% loss, got %.1f%%", result.LossPct)
}
if result.AvgRttS <= 0 {
t.Errorf("expected positive RTT, got %.6f s", result.AvgRttS)
}
t.Logf("avg UDP RTT: %.4f ms", result.AvgRttS*1000)
}
func TestProbeUDP_FullLoss(t *testing.T) {
// Bind a port then close it so nothing is listening.
// This avoids assuming a hardcoded port like 19999 is free.
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
pc.Close()
result := ProbeUDP("127.0.0.1", port, 5, 64, 200*time.Millisecond)
if result.LossPct != 100 {
t.Errorf("expected 100%% loss, got %.1f%%", result.LossPct)
}
}
func TestProbeUDP_PartialLoss(t *testing.T) {
tests := []struct {
name string
count int
dropEveryN int
expectedPct float64
}{
{"drop every 2nd (50%)", 10, 2, 50.0},
{"drop every 3rd (33.3%)", 9, 3, 100.0 / 3.0},
{"drop every 5th (20%)", 10, 5, 20.0},
{"drop every 10th (10%)", 10, 10, 10.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
port, cleanup := startLossyEchoListener(t, tt.dropEveryN)
defer cleanup()
result := ProbeUDP("127.0.0.1", port, tt.count, 64, 2*time.Second)
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
// Allow small floating point tolerance
diff := result.LossPct - tt.expectedPct
if diff < -0.1 || diff > 0.1 {
t.Errorf("expected %.1f%% loss, got %.1f%%", tt.expectedPct, result.LossPct)
}
t.Logf("loss: %.1f%% (expected %.1f%%)", result.LossPct, tt.expectedPct)
})
}
}
func TestProbeUDP_ZeroCount(t *testing.T) {
result := ProbeUDP("127.0.0.1", 12345, 0, 64, 200*time.Millisecond)
if result.Err == nil {
t.Error("expected error for count=0, got nil")
}
}
func TestProbeUDP_PacketFormat(t *testing.T) {
pkt := make([]byte, 64)
binary.BigEndian.PutUint32(pkt[0:4], udpMagic)
binary.BigEndian.PutUint32(pkt[4:8], 42)
binary.BigEndian.PutUint64(pkt[8:16], uint64(time.Now().UnixNano()))
magic := binary.BigEndian.Uint32(pkt[0:4])
if magic != 0x47504E47 {
t.Errorf("expected magic 0x47504E47, got 0x%X", magic)
}
seq := binary.BigEndian.Uint32(pkt[4:8])
if seq != 42 {
t.Errorf("expected seq 42, got %d", seq)
}
}
func TestEstimateHops(t *testing.T) {
tests := []struct {
ttl int
want int32
}{
{64, 0}, // same host, Linux
{63, 1}, // 1 hop, Linux
{56, 8}, // 8 hops, Linux
{128, 0}, // same host, Windows (TTL > 64 → initial=128)
{127, 1}, // 1 hop, Windows
{0, 0}, // invalid
}
for _, tt := range tests {
got := estimateHops(tt.ttl)
if got != tt.want {
t.Errorf("estimateHops(%d) = %d, want %d", tt.ttl, got, tt.want)
}
}
}

View File

@@ -93,10 +93,11 @@ func updatePingers(resultsChan chan<- PingAllPodsResult) {
// createPingers allocates a new pinger object for each new goldpinger Pod that's been discovered
// It also:
// (a) initializes a result object in checkResults to store info on that pod
// (b) starts a new goroutines to continuously ping the given pod.
// Each new goroutine waits for a given time before starting the continuous ping
// to prevent a thundering herd
//
// (a) initializes a result object in checkResults to store info on that pod
// (b) starts a new goroutines to continuously ping the given pod.
// Each new goroutine waits for a given time before starting the continuous ping
// to prevent a thundering herd
func createPingers(pingers map[string]*Pinger, newPods map[string]*GoldpingerPod, resultsChan chan<- PingAllPodsResult, refreshPeriod time.Duration) {
if len(newPods) == 0 {
// I have nothing to do
@@ -136,6 +137,11 @@ func destroyPingers(pingers map[string]*Pinger, deletedPods map[string]*Goldping
// Close the channel to stop pinging
close(pinger.stopChan)
// Clean up stale UDP metric labels for this peer
if GoldpingerConfig.UDPEnabled {
DeletePeerUDPMetrics(pod.HostIP, pod.PodIP)
}
// delete from pingers
delete(pingers, podName)
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -93,11 +91,15 @@ func (m *CheckAllPodResult) validateResponse(formats strfmt.Registry) error {
if m.Response != nil {
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}
@@ -122,12 +124,21 @@ func (m *CheckAllPodResult) ContextValidate(ctx context.Context, formats strfmt.
func (m *CheckAllPodResult) contextValidateResponse(ctx context.Context, formats strfmt.Registry) error {
if m.Response != nil {
if swag.IsZero(m.Response) { // not required
return nil
}
if err := m.Response.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"strconv"
"github.com/go-openapi/errors"
@@ -73,11 +71,15 @@ func (m *CheckAllResults) validateHosts(formats strfmt.Registry) error {
if m.Hosts[i] != nil {
if err := m.Hosts[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("hosts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("hosts" + "." + strconv.Itoa(i))
}
return err
}
}
@@ -117,11 +119,15 @@ func (m *CheckAllResults) validateResponses(formats strfmt.Registry) error {
}
if val, ok := m.Responses[k]; ok {
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("responses" + "." + k)
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("responses" + "." + k)
}
return err
}
}
@@ -158,12 +164,21 @@ func (m *CheckAllResults) contextValidateHosts(ctx context.Context, formats strf
for i := 0; i < len(m.Hosts); i++ {
if m.Hosts[i] != nil {
if swag.IsZero(m.Hosts[i]) { // not required
return nil
}
if err := m.Hosts[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("hosts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("hosts" + "." + strconv.Itoa(i))
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -56,11 +54,15 @@ func (m *CheckResults) validatePodResults(formats strfmt.Registry) error {
}
if val, ok := m.PodResults[k]; ok {
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("podResults" + "." + k)
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("podResults" + "." + k)
}
return err
}
}
@@ -77,11 +79,15 @@ func (m *CheckResults) validateProbeResults(formats strfmt.Registry) error {
if m.ProbeResults != nil {
if err := m.ProbeResults.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("probeResults")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("probeResults")
}
return err
}
}
@@ -124,12 +130,20 @@ func (m *CheckResults) contextValidatePodResults(ctx context.Context, formats st
func (m *CheckResults) contextValidateProbeResults(ctx context.Context, formats strfmt.Registry) error {
if swag.IsZero(m.ProbeResults) { // not required
return nil
}
if err := m.ProbeResults.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("probeResults")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("probeResults")
}
return err
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
@@ -60,7 +57,7 @@ func (m *ClusterHealthResults) Validate(formats strfmt.Registry) error {
func (m *ClusterHealthResults) validateOK(formats strfmt.Registry) error {
if err := validate.Required("OK", "body", bool(m.OK)); err != nil {
if err := validate.Required("OK", "body", m.OK); err != nil {
return err
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -64,11 +62,15 @@ func (m *PingResults) validateReceived(formats strfmt.Registry) error {
if m.Received != nil {
if err := m.Received.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("received")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("received")
}
return err
}
}
@@ -93,12 +95,21 @@ func (m *PingResults) ContextValidate(ctx context.Context, formats strfmt.Regist
func (m *PingResults) contextValidateReceived(ctx context.Context, formats strfmt.Registry) error {
if m.Received != nil {
if swag.IsZero(m.Received) { // not required
return nil
}
if err := m.Received.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("received")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("received")
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -37,6 +35,12 @@ type PodResult struct {
// error
Error string `json:"error,omitempty"`
// estimated network hop count from UDP TTL
HopCount int32 `json:"hop-count,omitempty"`
// UDP packet loss percentage (0-100)
LossPct float64 `json:"loss-pct,omitempty"`
// response
Response *PingResults `json:"response,omitempty"`
@@ -45,6 +49,9 @@ type PodResult struct {
// status code
StatusCode int32 `json:"status-code,omitempty"`
// average UDP round-trip time in milliseconds
UDPRttMs float64 `json:"udp-rtt-ms,omitempty"`
}
// Validate validates this pod result
@@ -116,11 +123,15 @@ func (m *PodResult) validateResponse(formats strfmt.Registry) error {
if m.Response != nil {
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}
@@ -145,12 +156,21 @@ func (m *PodResult) ContextValidate(ctx context.Context, formats strfmt.Registry
func (m *PodResult) contextValidateResponse(ctx context.Context, formats strfmt.Registry) error {
if m.Response != nil {
if swag.IsZero(m.Response) { // not required
return nil
}
if err := m.Response.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,15 +2,14 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
@@ -32,11 +31,15 @@ func (m ProbeResults) Validate(formats strfmt.Registry) error {
for i := 0; i < len(m[k]); i++ {
if err := m[k][i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName(k + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName(k + "." + strconv.Itoa(i))
}
return err
}
@@ -58,12 +61,20 @@ func (m ProbeResults) ContextValidate(ctx context.Context, formats strfmt.Regist
for i := 0; i < len(m[k]); i++ {
if swag.IsZero(m[k][i]) { // not required
return nil
}
if err := m[k][i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName(k + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName(k + "." + strconv.Itoa(i))
}
return err
}

View File

@@ -2,17 +2,17 @@
// Package restapi Goldpinger
//
// Schemes:
// http
// Host: localhost
// BasePath: /
// Version: 3.0.0
// Schemes:
// http
// Host: localhost
// BasePath: /
// Version: 3.0.0
//
// Consumes:
// - application/json
// Consumes:
// - application/json
//
// Produces:
// - application/json
// Produces:
// - application/json
//
// swagger:meta
package restapi

View File

@@ -2,9 +2,6 @@
package restapi
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
)
@@ -170,12 +167,6 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -203,10 +194,10 @@ func init() {
"type": "integer",
"format": "int32"
},
"httpResults": {
"probeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/HttpResults"
"$ref": "#/definitions/ProbeResults"
}
},
"responses": {
@@ -214,32 +205,20 @@ func init() {
"additionalProperties": {
"$ref": "#/definitions/CheckAllPodResult"
}
},
"tcpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/TcpResults"
}
}
}
},
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"httpResults": {
"$ref": "#/definitions/HttpResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
},
"tcpResults": {
"$ref": "#/definitions/TcpResults"
"probeResults": {
"$ref": "#/definitions/ProbeResults"
}
}
},
@@ -279,12 +258,6 @@ func init() {
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"HealthCheckResults": {
"type": "object",
"properties": {
@@ -302,12 +275,6 @@ func init() {
}
}
},
"HttpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"PingResults": {
"type": "object",
"properties": {
@@ -342,6 +309,16 @@ func init() {
"error": {
"type": "string"
},
"hop-count": {
"description": "estimated network hop count from UDP TTL",
"type": "integer",
"format": "int32"
},
"loss-pct": {
"description": "UDP packet loss percentage (0-100)",
"type": "number",
"format": "double"
},
"response": {
"$ref": "#/definitions/PingResults"
},
@@ -353,6 +330,11 @@ func init() {
"status-code": {
"type": "integer",
"format": "int32"
},
"udp-rtt-ms": {
"description": "average UDP round-trip time in milliseconds",
"type": "number",
"format": "double"
}
}
},
@@ -361,16 +343,22 @@ func init() {
"error": {
"type": "string"
},
"protocol": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"TcpResults": {
"ProbeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
"type": "array",
"items": {
"$ref": "#/definitions/ProbeResult"
}
}
}
}
@@ -528,12 +516,6 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -548,10 +530,10 @@ func init() {
"type": "integer",
"format": "int32"
},
"httpResults": {
"probeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/HttpResults"
"$ref": "#/definitions/ProbeResults"
}
},
"responses": {
@@ -559,12 +541,6 @@ func init() {
"additionalProperties": {
"$ref": "#/definitions/CheckAllPodResult"
}
},
"tcpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/TcpResults"
}
}
}
},
@@ -587,20 +563,14 @@ func init() {
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"httpResults": {
"$ref": "#/definitions/HttpResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
},
"tcpResults": {
"$ref": "#/definitions/TcpResults"
"probeResults": {
"$ref": "#/definitions/ProbeResults"
}
}
},
@@ -640,12 +610,6 @@ func init() {
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"HealthCheckResults": {
"type": "object",
"properties": {
@@ -663,12 +627,6 @@ func init() {
}
}
},
"HttpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"PingResults": {
"type": "object",
"properties": {
@@ -703,6 +661,16 @@ func init() {
"error": {
"type": "string"
},
"hop-count": {
"description": "estimated network hop count from UDP TTL",
"type": "integer",
"format": "int32"
},
"loss-pct": {
"description": "UDP packet loss percentage (0-100)",
"type": "number",
"format": "double"
},
"response": {
"$ref": "#/definitions/PingResults"
},
@@ -714,6 +682,11 @@ func init() {
"status-code": {
"type": "integer",
"format": "int32"
},
"udp-rtt-ms": {
"description": "average UDP round-trip time in milliseconds",
"type": "number",
"format": "double"
}
}
},
@@ -722,16 +695,22 @@ func init() {
"error": {
"type": "string"
},
"protocol": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"TcpResults": {
"ProbeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
"type": "array",
"items": {
"$ref": "#/definitions/ProbeResult"
}
}
}
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewCheckAllPods(ctx *middleware.Context, handler CheckAllPodsHandler) *Chec
return &CheckAllPods{Context: ctx, Handler: handler}
}
/* CheckAllPods swagger:route GET /check_all checkAllPods
/*
CheckAllPods swagger:route GET /check_all checkAllPods
Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
*/
type CheckAllPods struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *CheckAllPods) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewCheckAllPodsParams() CheckAllPodsParams {
//
// swagger:parameters checkAllPods
type CheckAllPodsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// CheckAllPodsOKCode is the HTTP code returned for type CheckAllPodsOK
const CheckAllPodsOKCode int = 200
/*CheckAllPodsOK Success, return response
/*
CheckAllPodsOK Success, return response
swagger:response checkAllPodsOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewCheckServicePods(ctx *middleware.Context, handler CheckServicePodsHandle
return &CheckServicePods{Context: ctx, Handler: handler}
}
/* CheckServicePods swagger:route GET /check checkServicePods
/*
CheckServicePods swagger:route GET /check checkServicePods
Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
*/
type CheckServicePods struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *CheckServicePods) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewCheckServicePodsParams() CheckServicePodsParams {
//
// swagger:parameters checkServicePods
type CheckServicePodsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// CheckServicePodsOKCode is the HTTP code returned for type CheckServicePodsOK
const CheckServicePodsOKCode int = 200
/*CheckServicePodsOK Success, return response
/*
CheckServicePodsOK Success, return response
swagger:response checkServicePodsOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewClusterHealth(ctx *middleware.Context, handler ClusterHealthHandler) *Cl
return &ClusterHealth{Context: ctx, Handler: handler}
}
/* ClusterHealth swagger:route GET /cluster_health clusterHealth
/*
ClusterHealth swagger:route GET /cluster_health clusterHealth
Checks the full graph. Returns a binary OK or not OK.
*/
type ClusterHealth struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *ClusterHealth) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewClusterHealthParams() ClusterHealthParams {
//
// swagger:parameters clusterHealth
type ClusterHealthParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// ClusterHealthOKCode is the HTTP code returned for type ClusterHealthOK
const ClusterHealthOKCode int = 200
/*ClusterHealthOK Healthy cluster
/*
ClusterHealthOK Healthy cluster
swagger:response clusterHealthOK
*/
@@ -60,7 +58,8 @@ func (o *ClusterHealthOK) WriteResponse(rw http.ResponseWriter, producer runtime
// ClusterHealthIMATeapotCode is the HTTP code returned for type ClusterHealthIMATeapot
const ClusterHealthIMATeapotCode int = 418
/*ClusterHealthIMATeapot Unhealthy cluster
/*
ClusterHealthIMATeapot Unhealthy cluster
swagger:response clusterHealthIMATeapot
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"net/http"
@@ -43,18 +40,32 @@ func NewGoldpingerAPI(spec *loads.Document) *GoldpingerAPI {
JSONProducer: runtime.JSONProducer(),
CheckAllPodsHandler: CheckAllPodsHandlerFunc(func(params CheckAllPodsParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation CheckAllPods has not yet been implemented")
}),
CheckServicePodsHandler: CheckServicePodsHandlerFunc(func(params CheckServicePodsParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation CheckServicePods has not yet been implemented")
}),
ClusterHealthHandler: ClusterHealthHandlerFunc(func(params ClusterHealthParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation ClusterHealth has not yet been implemented")
}),
HealthzHandler: HealthzHandlerFunc(func(params HealthzParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation Healthz has not yet been implemented")
}),
PingHandler: PingHandlerFunc(func(params PingParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation Ping has not yet been implemented")
}),
}
@@ -120,7 +131,7 @@ type GoldpingerAPI struct {
CommandLineOptionsGroups []swag.CommandLineOptionsGroup
// User defined logger function.
Logger func(string, ...interface{})
Logger func(string, ...any)
}
// UseRedoc for documentation at /docs
@@ -219,12 +230,12 @@ func (o *GoldpingerAPI) Authorizer() runtime.Authorizer {
}
// ConsumersFor gets the consumers for the specified media types.
//
// MIME type parameters are ignored here.
func (o *GoldpingerAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
result := make(map[string]runtime.Consumer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
if mt == "application/json" {
result["application/json"] = o.JSONConsumer
}
@@ -232,16 +243,17 @@ func (o *GoldpingerAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Con
result[mt] = c
}
}
return result
}
// ProducersFor gets the producers for the specified media types.
//
// MIME type parameters are ignored here.
func (o *GoldpingerAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
result := make(map[string]runtime.Producer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
if mt == "application/json" {
result["application/json"] = o.JSONProducer
}
@@ -249,6 +261,7 @@ func (o *GoldpingerAPI) ProducersFor(mediaTypes []string) map[string]runtime.Pro
result[mt] = p
}
}
return result
}
@@ -344,6 +357,6 @@ func (o *GoldpingerAPI) AddMiddlewareFor(method, path string, builder middleware
}
o.Init()
if h, ok := o.handlers[um][path]; ok {
o.handlers[method][path] = builder(h)
o.handlers[um][path] = builder(h)
}
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewHealthz(ctx *middleware.Context, handler HealthzHandler) *Healthz {
return &Healthz{Context: ctx, Handler: handler}
}
/* Healthz swagger:route GET /healthz healthz
/*
Healthz swagger:route GET /healthz healthz
The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
*/
type Healthz struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *Healthz) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewHealthzParams() HealthzParams {
//
// swagger:parameters healthz
type HealthzParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// HealthzOKCode is the HTTP code returned for type HealthzOK
const HealthzOKCode int = 200
/*HealthzOK Health check report
/*
HealthzOK Health check report
swagger:response healthzOK
*/
@@ -60,7 +58,8 @@ func (o *HealthzOK) WriteResponse(rw http.ResponseWriter, producer runtime.Produ
// HealthzServiceUnavailableCode is the HTTP code returned for type HealthzServiceUnavailable
const HealthzServiceUnavailableCode int = 503
/*HealthzServiceUnavailable Unhealthy service
/*
HealthzServiceUnavailable Unhealthy service
swagger:response healthzServiceUnavailable
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewPing(ctx *middleware.Context, handler PingHandler) *Ping {
return &Ping{Context: ctx, Handler: handler}
}
/* Ping swagger:route GET /ping ping
/*
Ping swagger:route GET /ping ping
return query stats
*/
type Ping struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *Ping) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewPingParams() PingParams {
//
// swagger:parameters ping
type PingParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// PingOKCode is the HTTP code returned for type PingOK
const PingOKCode int = 200
/*PingOK return success
/*
PingOK return success
swagger:response pingOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -7,8 +7,6 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
@@ -20,11 +18,12 @@ import (
"syscall"
"time"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
flags "github.com/jessevdk/go-flags"
"golang.org/x/net/netutil"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
"github.com/bloomberg/goldpinger/v3/pkg/restapi/operations"
)
@@ -81,7 +80,7 @@ type Server struct {
ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"`
KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"30s"`
httpServerL net.Listener
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
@@ -105,7 +104,7 @@ type Server struct {
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
func (s *Server) Logf(f string, args ...any) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
@@ -115,7 +114,7 @@ func (s *Server) Logf(f string, args ...interface{}) {
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
func (s *Server) Fatalf(f string, args ...any) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
@@ -189,8 +188,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at unix://%s", s.SocketPath)
go func(l net.Listener) {
defer wg.Done()
if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := domainSocket.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at unix://%s", s.SocketPath)
}(s.domainSocketL)
@@ -219,8 +218,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at http://%s", s.httpServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := httpServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at http://%s", l.Addr())
}(s.httpServerL)
@@ -274,14 +273,14 @@ func (s *Server) Serve() (err error) {
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate))
caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate))
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
return errors.New("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
@@ -312,8 +311,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at https://%s", s.httpsServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := httpsServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))

View File

@@ -378,16 +378,33 @@ var main = function(timeout){
if (!edge._data.OK) {
color = "red";
}
if (edge._data.OK && edge._data["loss-pct"] > 0) {
color = "#f0ad4e";
}
if ("isprobeResultsNode" in edge._data) {
type = "dashed";
}
var label = "";
if (edge._data["udp-rtt-ms"] > 0) {
label = edge._data["udp-rtt-ms"].toFixed(2) + "ms udp";
} else if (edge._data["response-time-ms"]) {
label = edge._data["response-time-ms"] + "ms";
} else if (edge._data.elapsed) {
label = edge._data.elapsed;
}
if (edge._data["loss-pct"] > 0) {
label += " " + edge._data["loss-pct"].toFixed(1) + "% loss";
}
if (edge._data["hop-count"] > 0) {
label += " " + edge._data["hop-count"] + " hops";
}
var edge = {
id: "e" + i,
source: edge.source,
target: edge.target,
type: type,
color: color,
label: edge._data.elapsed,
label: label,
size: 7
}
//console.log(edge);

View File

@@ -61,6 +61,18 @@ definitions:
type: number
format: int64
description: wall clock time in milliseconds
loss-pct:
type: number
format: double
description: UDP packet loss percentage (0-100)
hop-count:
type: integer
format: int32
description: estimated network hop count from UDP TTL
udp-rtt-ms:
type: number
format: double
description: average UDP round-trip time in milliseconds
CheckResults:
type: object
properties: