Private
Public Access
1
0

fix(api): protocol-aware monitor filtering for multi-protocol monitors
All checks were successful
continuous-integration/drone/push Build is passing

Servers with monitor filtering returned incorrect results when monitors
have same names but different protocols (v4/v6). Monitor lookup now
considers both name and IP version to match the correct protocol.

- Add GetMonitorByNameAndIPVersion SQL query with protocol matching
- Update history parameter parsing to use server IP version context
- Fix both /scores/{ip}/log and Grafana endpoints
- Remove unused GetMonitorByName query

Fixes abh/ntppool#264
Reported-by: Anssi Johansson <https://github.com/avijc>
This commit is contained in:
2025-07-27 00:37:49 -07:00
parent 8262b1442f
commit eb5459abf3
6 changed files with 135 additions and 103 deletions

View File

@@ -8,7 +8,6 @@ package ntpdb
import ( import (
"context" "context"
"database/sql"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
@@ -82,14 +81,14 @@ func (_d QuerierTxWithTracing) Commit(ctx context.Context) (err error) {
return _d.QuerierTx.Commit(ctx) return _d.QuerierTx.Commit(ctx)
} }
// GetMonitorByName implements QuerierTx // GetMonitorByNameAndIPVersion implements QuerierTx
func (_d QuerierTxWithTracing) GetMonitorByName(ctx context.Context, tlsName sql.NullString) (m1 Monitor, err error) { func (_d QuerierTxWithTracing) GetMonitorByNameAndIPVersion(ctx context.Context, arg GetMonitorByNameAndIPVersionParams) (m1 Monitor, err error) {
ctx, _span := otel.Tracer(_d._instance).Start(ctx, "QuerierTx.GetMonitorByName") ctx, _span := otel.Tracer(_d._instance).Start(ctx, "QuerierTx.GetMonitorByNameAndIPVersion")
defer func() { defer func() {
if _d._spanDecorator != nil { if _d._spanDecorator != nil {
_d._spanDecorator(_span, map[string]interface{}{ _d._spanDecorator(_span, map[string]interface{}{
"ctx": ctx, "ctx": ctx,
"tlsName": tlsName}, map[string]interface{}{ "arg": arg}, map[string]interface{}{
"m1": m1, "m1": m1,
"err": err}) "err": err})
} else if err != nil { } else if err != nil {
@@ -103,7 +102,7 @@ func (_d QuerierTxWithTracing) GetMonitorByName(ctx context.Context, tlsName sql
_span.End() _span.End()
}() }()
return _d.QuerierTx.GetMonitorByName(ctx, tlsName) return _d.QuerierTx.GetMonitorByNameAndIPVersion(ctx, arg)
} }
// GetMonitorsByID implements QuerierTx // GetMonitorsByID implements QuerierTx

View File

@@ -6,11 +6,10 @@ package ntpdb
import ( import (
"context" "context"
"database/sql"
) )
type Querier interface { type Querier interface {
GetMonitorByName(ctx context.Context, tlsName sql.NullString) (Monitor, error) GetMonitorByNameAndIPVersion(ctx context.Context, arg GetMonitorByNameAndIPVersionParams) (Monitor, error)
GetMonitorsByID(ctx context.Context, monitorids []uint32) ([]Monitor, error) GetMonitorsByID(ctx context.Context, monitorids []uint32) ([]Monitor, error)
GetServerByID(ctx context.Context, id uint32) (Server, error) GetServerByID(ctx context.Context, id uint32) (Server, error)
GetServerByIP(ctx context.Context, ip string) (Server, error) GetServerByIP(ctx context.Context, ip string) (Server, error)

View File

@@ -12,16 +12,24 @@ import (
"time" "time"
) )
const getMonitorByName = `-- name: GetMonitorByName :one const getMonitorByNameAndIPVersion = `-- name: GetMonitorByNameAndIPVersion :one
select id, id_token, type, user_id, account_id, hostname, location, ip, ip_version, tls_name, api_key, status, config, client_version, last_seen, last_submit, created_on, deleted_on, is_current from monitors select id, id_token, type, user_id, account_id, hostname, location, ip, ip_version, tls_name, api_key, status, config, client_version, last_seen, last_submit, created_on, deleted_on, is_current from monitors
where where
tls_name like ? tls_name like ? AND
ip_version = ? AND
is_current = 1 AND
status != 'deleted'
order by id order by id
limit 1 limit 1
` `
func (q *Queries) GetMonitorByName(ctx context.Context, tlsName sql.NullString) (Monitor, error) { type GetMonitorByNameAndIPVersionParams struct {
row := q.db.QueryRowContext(ctx, getMonitorByName, tlsName) TlsName sql.NullString `db:"tls_name" json:"tls_name"`
IpVersion NullMonitorsIpVersion `db:"ip_version" json:"ip_version"`
}
func (q *Queries) GetMonitorByNameAndIPVersion(ctx context.Context, arg GetMonitorByNameAndIPVersionParams) (Monitor, error) {
row := q.db.QueryRowContext(ctx, getMonitorByNameAndIPVersion, arg.TlsName, arg.IpVersion)
var i Monitor var i Monitor
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,

View File

@@ -47,10 +47,13 @@ select * from servers
where where
ip = sqlc.arg(ip); ip = sqlc.arg(ip);
-- name: GetMonitorByName :one -- name: GetMonitorByNameAndIPVersion :one
select * from monitors select * from monitors
where where
tls_name like sqlc.arg('tls_name') tls_name like sqlc.arg('tls_name') AND
ip_version = sqlc.arg('ip_version') AND
is_current = 1 AND
status != 'deleted'
order by id order by id
limit 1; limit 1;

View File

@@ -42,59 +42,59 @@ type timeRangeParams struct {
} }
// parseTimeRangeParams parses and validates time range parameters // parseTimeRangeParams parses and validates time range parameters
func (srv *Server) parseTimeRangeParams(ctx context.Context, c echo.Context) (timeRangeParams, error) { func (srv *Server) parseTimeRangeParams(ctx context.Context, c echo.Context, server ntpdb.Server) (timeRangeParams, error) {
log := logger.FromContext(ctx) log := logger.FromContext(ctx)
// Start with existing parameter parsing logic // Start with existing parameter parsing logic
baseParams, err := srv.getHistoryParameters(ctx, c) baseParams, err := srv.getHistoryParameters(ctx, c, server)
if err != nil { if err != nil {
return timeRangeParams{}, err return timeRangeParams{}, err
} }
trParams := timeRangeParams{ trParams := timeRangeParams{
historyParameters: baseParams, historyParameters: baseParams,
maxDataPoints: 50000, // default maxDataPoints: 50000, // default
} }
// Parse from timestamp (required) // Parse from timestamp (required)
fromParam := c.QueryParam("from") fromParam := c.QueryParam("from")
if fromParam == "" { if fromParam == "" {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "from parameter is required") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "from parameter is required")
} }
fromSec, err := strconv.ParseInt(fromParam, 10, 64) fromSec, err := strconv.ParseInt(fromParam, 10, 64)
if err != nil { if err != nil {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "invalid from timestamp format") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "invalid from timestamp format")
} }
trParams.from = time.Unix(fromSec, 0) trParams.from = time.Unix(fromSec, 0)
// Parse to timestamp (required) // Parse to timestamp (required)
toParam := c.QueryParam("to") toParam := c.QueryParam("to")
if toParam == "" { if toParam == "" {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "to parameter is required") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "to parameter is required")
} }
toSec, err := strconv.ParseInt(toParam, 10, 64) toSec, err := strconv.ParseInt(toParam, 10, 64)
if err != nil { if err != nil {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "invalid to timestamp format") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "invalid to timestamp format")
} }
trParams.to = time.Unix(toSec, 0) trParams.to = time.Unix(toSec, 0)
// Validate time range // Validate time range
if trParams.from.Equal(trParams.to) || trParams.from.After(trParams.to) { if trParams.from.Equal(trParams.to) || trParams.from.After(trParams.to) {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "from must be before to") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "from must be before to")
} }
// Check minimum range (1 second) // Check minimum range (1 second)
if trParams.to.Sub(trParams.from) < time.Second { if trParams.to.Sub(trParams.from) < time.Second {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "time range must be at least 1 second") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "time range must be at least 1 second")
} }
// Check maximum range (90 days) // Check maximum range (90 days)
if trParams.to.Sub(trParams.from) > 90*24*time.Hour { if trParams.to.Sub(trParams.from) > 90*24*time.Hour {
return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "time range cannot exceed 90 days") return timeRangeParams{}, echo.NewHTTPError(http.StatusBadRequest, "time range cannot exceed 90 days")
} }
// Parse maxDataPoints (optional) // Parse maxDataPoints (optional)
if maxDataPointsParam := c.QueryParam("maxDataPoints"); maxDataPointsParam != "" { if maxDataPointsParam := c.QueryParam("maxDataPoints"); maxDataPointsParam != "" {
maxDP, err := strconv.Atoi(maxDataPointsParam) maxDP, err := strconv.Atoi(maxDataPointsParam)
@@ -108,10 +108,10 @@ func (srv *Server) parseTimeRangeParams(ctx context.Context, c echo.Context) (ti
trParams.maxDataPoints = maxDP trParams.maxDataPoints = maxDP
} }
} }
// Parse interval (optional, for future downsampling) // Parse interval (optional, for future downsampling)
trParams.interval = c.QueryParam("interval") trParams.interval = c.QueryParam("interval")
log.DebugContext(ctx, "parsed time range params", log.DebugContext(ctx, "parsed time range params",
"from", trParams.from, "from", trParams.from,
"to", trParams.to, "to", trParams.to,
@@ -119,7 +119,7 @@ func (srv *Server) parseTimeRangeParams(ctx context.Context, c echo.Context) (ti
"interval", trParams.interval, "interval", trParams.interval,
"monitor", trParams.monitorID, "monitor", trParams.monitorID,
) )
return trParams, nil return trParams, nil
} }
@@ -137,7 +137,7 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
// Group data by monitor_id (one series per monitor) // Group data by monitor_id (one series per monitor)
monitorData := make(map[int][]ntpdb.LogScore) monitorData := make(map[int][]ntpdb.LogScore)
monitorInfo := make(map[int]ntpdb.Monitor) monitorInfo := make(map[int]ntpdb.Monitor)
// Group log scores by monitor ID // Group log scores by monitor ID
skippedInvalidMonitors := 0 skippedInvalidMonitors := 0
for _, ls := range history.LogScores { for _, ls := range history.LogScores {
@@ -148,7 +148,7 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
monitorID := int(ls.MonitorID.Int32) monitorID := int(ls.MonitorID.Int32)
monitorData[monitorID] = append(monitorData[monitorID], ls) monitorData[monitorID] = append(monitorData[monitorID], ls)
} }
// Debug logging for transformation // Debug logging for transformation
logger.Setup().Info("transformation grouping debug", logger.Setup().Info("transformation grouping debug",
"total_log_scores", len(history.LogScores), "total_log_scores", len(history.LogScores),
@@ -168,30 +168,30 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
return counts return counts
}(), }(),
) )
// Index monitors by ID for quick lookup // Index monitors by ID for quick lookup
for _, monitor := range monitors { for _, monitor := range monitors {
monitorInfo[int(monitor.ID)] = monitor monitorInfo[int(monitor.ID)] = monitor
} }
var response GrafanaTimeSeriesResponse var response GrafanaTimeSeriesResponse
// Create one table series per monitor // Create one table series per monitor
logger.Setup().Info("creating grafana series", logger.Setup().Info("creating grafana series",
"monitor_data_entries", len(monitorData), "monitor_data_entries", len(monitorData),
) )
for monitorID, logScores := range monitorData { for monitorID, logScores := range monitorData {
if len(logScores) == 0 { if len(logScores) == 0 {
logger.Setup().Info("skipping monitor with no data", "monitor_id", monitorID) logger.Setup().Info("skipping monitor with no data", "monitor_id", monitorID)
continue continue
} }
logger.Setup().Info("processing monitor series", logger.Setup().Info("processing monitor series",
"monitor_id", monitorID, "monitor_id", monitorID,
"log_scores_count", len(logScores), "log_scores_count", len(logScores),
) )
// Get monitor name from history.Monitors map or from monitor info // Get monitor name from history.Monitors map or from monitor info
monitorName := "unknown" monitorName := "unknown"
if name, exists := history.Monitors[monitorID]; exists && name != "" { if name, exists := history.Monitors[monitorID]; exists && name != "" {
@@ -199,20 +199,20 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
} else if monitor, exists := monitorInfo[monitorID]; exists { } else if monitor, exists := monitorInfo[monitorID]; exists {
monitorName = monitor.DisplayName() monitorName = monitor.DisplayName()
} }
// Build target name and tags // Build target name and tags
sanitizedName := sanitizeMonitorName(monitorName) sanitizedName := sanitizeMonitorName(monitorName)
target := "monitor{name=" + sanitizedName + "}" target := "monitor{name=" + sanitizedName + "}"
tags := map[string]string{ tags := map[string]string{
"monitor_id": strconv.Itoa(monitorID), "monitor_id": strconv.Itoa(monitorID),
"monitor_name": monitorName, "monitor_name": monitorName,
"type": "monitor", "type": "monitor",
} }
// Add status (we'll use active as default since we have data for this monitor) // Add status (we'll use active as default since we have data for this monitor)
tags["status"] = "active" tags["status"] = "active"
// Define table columns // Define table columns
columns := []ColumnDef{ columns := []ColumnDef{
{Text: "time", Type: "time"}, {Text: "time", Type: "time"},
@@ -220,19 +220,19 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
{Text: "rtt", Type: "number", Unit: "ms"}, {Text: "rtt", Type: "number", Unit: "ms"},
{Text: "offset", Type: "number", Unit: "s"}, {Text: "offset", Type: "number", Unit: "s"},
} }
// Build values array // Build values array
var values [][]interface{} var values [][]interface{}
for _, ls := range logScores { for _, ls := range logScores {
// Convert timestamp to milliseconds // Convert timestamp to milliseconds
timestampMs := ls.Ts.Unix() * 1000 timestampMs := ls.Ts.Unix() * 1000
// Create row: [time, score, rtt, offset] // Create row: [time, score, rtt, offset]
row := []interface{}{ row := []interface{}{
timestampMs, timestampMs,
ls.Score, ls.Score,
} }
// Add RTT (convert from microseconds to milliseconds, handle null) // Add RTT (convert from microseconds to milliseconds, handle null)
if ls.Rtt.Valid { if ls.Rtt.Valid {
rttMs := float64(ls.Rtt.Int32) / 1000.0 rttMs := float64(ls.Rtt.Int32) / 1000.0
@@ -240,17 +240,17 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
} else { } else {
row = append(row, nil) row = append(row, nil)
} }
// Add offset (handle null) // Add offset (handle null)
if ls.Offset.Valid { if ls.Offset.Valid {
row = append(row, ls.Offset.Float64) row = append(row, ls.Offset.Float64)
} else { } else {
row = append(row, nil) row = append(row, nil)
} }
values = append(values, row) values = append(values, row)
} }
// Create table series // Create table series
series := GrafanaTableSeries{ series := GrafanaTableSeries{
Target: target, Target: target,
@@ -258,21 +258,21 @@ func transformToGrafanaTableFormat(history *logscores.LogScoreHistory, monitors
Columns: columns, Columns: columns,
Values: values, Values: values,
} }
response = append(response, series) response = append(response, series)
logger.Setup().Info("created series for monitor", logger.Setup().Info("created series for monitor",
"monitor_id", monitorID, "monitor_id", monitorID,
"target", series.Target, "target", series.Target,
"values_count", len(series.Values), "values_count", len(series.Values),
) )
} }
logger.Setup().Info("transformation complete", logger.Setup().Info("transformation complete",
"final_response_count", len(response), "final_response_count", len(response),
"response_is_nil", response == nil, "response_is_nil", response == nil,
) )
return response return response
} }
@@ -291,18 +291,7 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "invalid mode - only json supported") return echo.NewHTTPError(http.StatusNotFound, "invalid mode - only json supported")
} }
// Parse and validate time range parameters // Find and validate server first
params, err := srv.parseTimeRangeParams(ctx, c)
if err != nil {
if he, ok := err.(*echo.HTTPError); ok {
return he
}
log.ErrorContext(ctx, "parse time range parameters", "err", err)
span.RecordError(err)
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
}
// Find and validate server
server, err := srv.FindServer(ctx, c.Param("server")) server, err := srv.FindServer(ctx, c.Param("server"))
if err != nil { if err != nil {
log.ErrorContext(ctx, "find server", "err", err) log.ErrorContext(ctx, "find server", "err", err)
@@ -321,6 +310,17 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "server not found") return echo.NewHTTPError(http.StatusNotFound, "server not found")
} }
// Parse and validate time range parameters
params, err := srv.parseTimeRangeParams(ctx, c, server)
if err != nil {
if he, ok := err.(*echo.HTTPError); ok {
return he
}
log.ErrorContext(ctx, "parse time range parameters", "err", err)
span.RecordError(err)
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
}
// Query ClickHouse for time range data // Query ClickHouse for time range data
log.InfoContext(ctx, "executing clickhouse time range query", log.InfoContext(ctx, "executing clickhouse time range query",
"server_id", server.ID, "server_id", server.ID,
@@ -331,10 +331,10 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
"max_data_points", params.maxDataPoints, "max_data_points", params.maxDataPoints,
"time_range_duration", params.to.Sub(params.from).String(), "time_range_duration", params.to.Sub(params.from).String(),
) )
logScores, err := srv.ch.LogscoresTimeRange(ctx, int(server.ID), params.monitorID, params.from, params.to, params.maxDataPoints) logScores, err := srv.ch.LogscoresTimeRange(ctx, int(server.ID), params.monitorID, params.from, params.to, params.maxDataPoints)
if err != nil { if err != nil {
log.ErrorContext(ctx, "clickhouse time range query", "err", err, log.ErrorContext(ctx, "clickhouse time range query", "err", err,
"server_id", server.ID, "server_id", server.ID,
"monitor_id", params.monitorID, "monitor_id", params.monitorID,
"from", params.from, "from", params.from,
@@ -343,14 +343,16 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
span.RecordError(err) span.RecordError(err)
return echo.NewHTTPError(http.StatusInternalServerError, "internal error") return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
} }
log.InfoContext(ctx, "clickhouse query results", log.InfoContext(ctx, "clickhouse query results",
"server_id", server.ID, "server_id", server.ID,
"rows_returned", len(logScores), "rows_returned", len(logScores),
"first_few_ids", func() []uint64 { "first_few_ids", func() []uint64 {
ids := make([]uint64, 0, 3) ids := make([]uint64, 0, 3)
for i, ls := range logScores { for i, ls := range logScores {
if i >= 3 { break } if i >= 3 {
break
}
ids = append(ids, ls.ID) ids = append(ids, ls.ID)
} }
return ids return ids
@@ -374,7 +376,7 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
} }
} }
} }
log.InfoContext(ctx, "monitor processing", log.InfoContext(ctx, "monitor processing",
"unique_monitor_ids", monitorIDs, "unique_monitor_ids", monitorIDs,
"monitor_count", len(monitorIDs), "monitor_count", len(monitorIDs),
@@ -400,7 +402,7 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
ID: lsm.ID, ID: lsm.ID,
} }
monitors = append(monitors, tempMon) monitors = append(monitors, tempMon)
// Update monitor name in history // Update monitor name in history
history.Monitors[int(lsm.ID)] = tempMon.DisplayName() history.Monitors[int(lsm.ID)] = tempMon.DisplayName()
} }
@@ -413,9 +415,9 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
"monitors_count", len(monitors), "monitors_count", len(monitors),
"history_monitors", history.Monitors, "history_monitors", history.Monitors,
) )
grafanaResponse := transformToGrafanaTableFormat(history, monitors) grafanaResponse := transformToGrafanaTableFormat(history, monitors)
log.InfoContext(ctx, "grafana transformation complete", log.InfoContext(ctx, "grafana transformation complete",
"response_series_count", len(grafanaResponse), "response_series_count", len(grafanaResponse),
"response_preview", func() interface{} { "response_preview", func() interface{} {
@@ -424,14 +426,18 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
} }
first := grafanaResponse[0] first := grafanaResponse[0]
return map[string]interface{}{ return map[string]interface{}{
"target": first.Target, "target": first.Target,
"tags": first.Tags, "tags": first.Tags,
"columns_count": len(first.Columns), "columns_count": len(first.Columns),
"values_count": len(first.Values), "values_count": len(first.Values),
"first_few_values": func() [][]interface{} { "first_few_values": func() [][]interface{} {
if len(first.Values) == 0 { return [][]interface{}{} } if len(first.Values) == 0 {
return [][]interface{}{}
}
count := 2 count := 2
if len(first.Values) < count { count = len(first.Values) } if len(first.Values) < count {
count = len(first.Values)
}
return first.Values[:count] return first.Values[:count]
}(), }(),
} }
@@ -445,7 +451,7 @@ func (srv *Server) scoresTimeRange(c echo.Context) error {
c.Response().Header().Set("Access-Control-Allow-Origin", "*") c.Response().Header().Set("Access-Control-Allow-Origin", "*")
c.Response().Header().Set("Content-Type", "application/json") c.Response().Header().Set("Content-Type", "application/json")
log.InfoContext(ctx, "time range response final", log.InfoContext(ctx, "time range response final",
"server_id", server.ID, "server_id", server.ID,
"server_ip", server.Ip, "server_ip", server.Ip,
"monitor_id", params.monitorID, "monitor_id", params.monitorID,
@@ -466,7 +472,7 @@ func (srv *Server) testGrafanaTable(c echo.Context) error {
ctx, span := tracing.Tracer().Start(c.Request().Context(), "testGrafanaTable") ctx, span := tracing.Tracer().Start(c.Request().Context(), "testGrafanaTable")
defer span.End() defer span.End()
log.InfoContext(ctx, "serving test Grafana table format", log.InfoContext(ctx, "serving test Grafana table format",
"remote_ip", c.RealIP(), "remote_ip", c.RealIP(),
"user_agent", c.Request().UserAgent(), "user_agent", c.Request().UserAgent(),
) )
@@ -520,14 +526,14 @@ func (srv *Server) testGrafanaTable(c echo.Context) error {
// Add CORS header for browser testing // Add CORS header for browser testing
c.Response().Header().Set("Access-Control-Allow-Origin", "*") c.Response().Header().Set("Access-Control-Allow-Origin", "*")
c.Response().Header().Set("Content-Type", "application/json") c.Response().Header().Set("Content-Type", "application/json")
// Set cache control similar to other endpoints // Set cache control similar to other endpoints
c.Response().Header().Set("Cache-Control", "public,max-age=60") c.Response().Header().Set("Cache-Control", "public,max-age=60")
log.InfoContext(ctx, "test Grafana table response sent", log.InfoContext(ctx, "test Grafana table response sent",
"series_count", len(sampleData), "series_count", len(sampleData),
"response_size_approx", "~1KB", "response_size_approx", "~1KB",
) )
return c.JSON(http.StatusOK, sampleData) return c.JSON(http.StatusOK, sampleData)
} }

View File

@@ -69,7 +69,7 @@ type historyParameters struct {
fullHistory bool fullHistory bool
} }
func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context) (historyParameters, error) { func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context, server ntpdb.Server) (historyParameters, error) {
log := logger.FromContext(ctx) log := logger.FromContext(ctx)
p := historyParameters{} p := historyParameters{}
@@ -94,9 +94,18 @@ func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context) (hi
switch monitorParam { switch monitorParam {
case "": case "":
name := "recentmedian.scores.ntp.dev" name := "recentmedian.scores.ntp.dev"
monitor, err := q.GetMonitorByName(ctx, sql.NullString{Valid: true, String: name}) var ipVersion ntpdb.NullMonitorsIpVersion
if server.IpVersion == ntpdb.ServersIpVersionV4 {
ipVersion = ntpdb.NullMonitorsIpVersion{MonitorsIpVersion: ntpdb.MonitorsIpVersionV4, Valid: true}
} else {
ipVersion = ntpdb.NullMonitorsIpVersion{MonitorsIpVersion: ntpdb.MonitorsIpVersionV6, Valid: true}
}
monitor, err := q.GetMonitorByNameAndIPVersion(ctx, ntpdb.GetMonitorByNameAndIPVersionParams{
TlsName: sql.NullString{Valid: true, String: name},
IpVersion: ipVersion,
})
if err != nil { if err != nil {
log.Warn("could not find monitor", "name", name, "err", err) log.Warn("could not find monitor", "name", name, "ip_version", server.IpVersion, "err", err)
} }
monitorID = monitor.ID monitorID = monitor.ID
case "*": case "*":
@@ -113,12 +122,21 @@ func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context) (hi
} }
monitorParam = monitorParam + ".%" monitorParam = monitorParam + ".%"
monitor, err := q.GetMonitorByName(ctx, sql.NullString{Valid: true, String: monitorParam}) var ipVersion ntpdb.NullMonitorsIpVersion
if server.IpVersion == ntpdb.ServersIpVersionV4 {
ipVersion = ntpdb.NullMonitorsIpVersion{MonitorsIpVersion: ntpdb.MonitorsIpVersionV4, Valid: true}
} else {
ipVersion = ntpdb.NullMonitorsIpVersion{MonitorsIpVersion: ntpdb.MonitorsIpVersionV6, Valid: true}
}
monitor, err := q.GetMonitorByNameAndIPVersion(ctx, ntpdb.GetMonitorByNameAndIPVersionParams{
TlsName: sql.NullString{Valid: true, String: monitorParam},
IpVersion: ipVersion,
})
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return p, echo.NewHTTPError(http.StatusNotFound, "monitor not found").WithInternal(err) return p, echo.NewHTTPError(http.StatusNotFound, "monitor not found").WithInternal(err)
} }
log.WarnContext(ctx, "could not find monitor", "name", monitorParam, "err", err) log.WarnContext(ctx, "could not find monitor", "name", monitorParam, "ip_version", server.IpVersion, "err", err)
return p, echo.NewHTTPError(http.StatusNotFound, "monitor not found (sql)") return p, echo.NewHTTPError(http.StatusNotFound, "monitor not found (sql)")
} }
monitorID = monitor.ID monitorID = monitor.ID
@@ -127,7 +145,7 @@ func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context) (hi
} }
p.monitorID = int(monitorID) p.monitorID = int(monitorID)
log.DebugContext(ctx, "monitor param", "monitor", monitorID) log.DebugContext(ctx, "monitor param", "monitor", monitorID, "ip_version", server.IpVersion)
since, _ := strconv.ParseInt(c.QueryParam("since"), 10, 64) // defaults to 0 so don't care if it parses since, _ := strconv.ParseInt(c.QueryParam("since"), 10, 64) // defaults to 0 so don't care if it parses
if since > 0 { if since > 0 {
@@ -171,16 +189,6 @@ func (srv *Server) history(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "invalid mode") return echo.NewHTTPError(http.StatusNotFound, "invalid mode")
} }
p, err := srv.getHistoryParameters(ctx, c)
if err != nil {
if he, ok := err.(*echo.HTTPError); ok {
return he
}
log.ErrorContext(ctx, "get history parameters", "err", err)
span.RecordError(err)
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
}
server, err := srv.FindServer(ctx, c.Param("server")) server, err := srv.FindServer(ctx, c.Param("server"))
if err != nil { if err != nil {
log.ErrorContext(ctx, "find server", "err", err) log.ErrorContext(ctx, "find server", "err", err)
@@ -199,6 +207,16 @@ func (srv *Server) history(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "server not found") return echo.NewHTTPError(http.StatusNotFound, "server not found")
} }
p, err := srv.getHistoryParameters(ctx, c, server)
if err != nil {
if he, ok := err.(*echo.HTTPError); ok {
return he
}
log.ErrorContext(ctx, "get history parameters", "err", err)
span.RecordError(err)
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
}
p.server = server p.server = server
var history *logscores.LogScoreHistory var history *logscores.LogScoreHistory
@@ -456,4 +474,3 @@ func setHistoryCacheControl(c echo.Context, history *logscores.LogScoreHistory)
} }
} }
} }