Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions core/observability.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (n *Nylon) startObservability() error {
mux.HandleFunc("/readyz", n.handleReady)
mux.HandleFunc("/metrics", n.handleMetrics)
mux.HandleFunc("/discovery", n.handleDiscovery)
mux.HandleFunc("/status", n.handleStatusJSON)

obs := &observabilityServer{
listener: listener,
Expand Down Expand Up @@ -131,6 +132,23 @@ func (n *Nylon) handleMetrics(w http.ResponseWriter, r *http.Request) {
writePrometheusMetrics(w, status)
}

func (n *Nylon) handleStatusJSON(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), observabilityTimeout)
defer cancel()
status, err := n.statusSnapshot(ctx)
if err != nil {
http.Error(w, "status unavailable", http.StatusServiceUnavailable)
return
}
data, err := pjMarshal.Marshal(status)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(data)
}

func (n *Nylon) handleDiscovery(w http.ResponseWriter, _ *http.Request) {
_, port, err := net.SplitHostPort(n.LocalCfg.ObservabilityAddr)
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions core/observability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,14 @@ func TestPrometheusMetrics(t *testing.T) {
require.NotContains(t, output, `nylon_wireguard_peer_latest_handshake_seconds{peer="bob"} 1.786`)
require.Equal(t, 1, strings.Count(output, "# HELP nylon_wireguard_peer_transmit_bytes_total "))
}

func TestObservabilityStatusUnavailable(t *testing.T) {
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)
// nothing runs the dispatch loop, so the snapshot times out
n := &Nylon{Context: ctx, DispatchChannel: make(chan func() error)}

rec := httptest.NewRecorder()
n.handleStatusJSON(rec, httptest.NewRequest(http.MethodGet, "/status", nil))
require.Equal(t, http.StatusServiceUnavailable, rec.Code)
}
2 changes: 2 additions & 0 deletions docs/guides/observability.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Restart nylon after changing the node configuration. You can then check the loca
curl http://127.0.0.1:9090/healthz
curl http://127.0.0.1:9090/readyz
curl http://127.0.0.1:9090/metrics
curl http://127.0.0.1:9090/status
```

:::caution
Expand All @@ -34,6 +35,7 @@ The observability server does not provide authentication or TLS. Bind it to a tr
| `/healthz` | Returns `200 OK` while the daemon is running and `503 Service Unavailable` while it is shutting down. |
| `/readyz` | Returns `200 OK` when nylon's internal event loop responds, or `503 Service Unavailable` if it cannot respond within one second. It does not check whether every peer is reachable. |
| `/metrics` | Returns metrics in the Prometheus text exposition format. Returns `503 Service Unavailable` if nylon cannot collect a status snapshot within one second. |
| `/status` | Returns the node's status as JSON, the same object `nylon status --json` prints under `status`. Returns `503 Service Unavailable` if nylon cannot collect a snapshot within one second. |
| `/discovery` | Returns Prometheus HTTP service-discovery targets for the addresses in `central.yaml`. |

## Configure Prometheus
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ no_net_configure: false # if true, nylon won't touch system routes or interfaces
log_path: "" # write logs to this file (empty = stderr only)
interface_name: "" # override the interface name (default: "nylon", or utunX on macOS)
dns_resolvers: [] # DNS servers for nylon's own lookups, e.g. ["1.1.1.1:53"]
observability_addr: "" # e.g. "0.0.0.0:9090"; enables /metrics, /healthz, /readyz, and /discovery
observability_addr: "" # e.g. "0.0.0.0:9090"; enables /metrics, /healthz, /readyz, /discovery, and /status

# Bootstrap: fetch central.yaml from a remote bundle on first start
dist:
Expand Down