fix(service ping): correct endpoint, validate and randomize default interval (#10166)

# Which Problems Are Solved

The production endpoint of the service ping was wrong.
Additionally we discussed in the sprint review, that we could randomize
the default interval to prevent all systems to report data at the very
same time and also require a minimal interval.

# How the Problems Are Solved

- fixed the endpoint
- If the interval is set to @daily (default), we generate a random time
(minute, hour) as a cron format.
- Check if the interval is more than 30min and return an error if not.
- Fixed yaml indent on `ResourceCount`

# Additional Changes

None

# Additional Context

as discussed internally
This commit is contained in:
Livio Spring
2025-07-04 09:45:15 -04:00
committed by GitHub
parent 26ec29a513
commit 82cd1cee08
3 changed files with 125 additions and 7 deletions

View File

@@ -3,7 +3,10 @@ package serviceping
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"time"
"github.com/muhlemmer/gu"
"github.com/riverqueue/river"
@@ -15,11 +18,13 @@ import (
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/queue"
"github.com/zitadel/zitadel/internal/v2/system"
"github.com/zitadel/zitadel/internal/zerrors"
analytics "github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta"
)
const (
QueueName = "service_ping_report"
QueueName = "service_ping_report"
minInterval = 30 * time.Minute
)
var (
@@ -238,7 +243,7 @@ func Start(config *Config, q *queue.Queue) error {
if !config.Enabled {
return nil
}
schedule, err := cron.ParseStandard(config.Interval)
schedule, err := parseAndValidateSchedule(config.Interval)
if err != nil {
return err
}
@@ -250,3 +255,39 @@ func Start(config *Config, q *queue.Queue) error {
)
return nil
}
func parseAndValidateSchedule(interval string) (cron.Schedule, error) {
if interval == "@daily" {
interval = randomizeDaily()
}
schedule, err := cron.ParseStandard(interval)
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "SERV-NJqiof", "invalid interval")
}
var intervalDuration time.Duration
switch s := schedule.(type) {
case *cron.SpecSchedule:
// For cron.SpecSchedule, we need to calculate the interval duration
// by getting the next time and subtracting it from the time after that.
// This is because the schedule could be a specific time, that is less than 30 minutes away,
// but still run only once a day and therefore is valid.
next := s.Next(time.Now())
nextAfter := s.Next(next)
intervalDuration = nextAfter.Sub(next)
case cron.ConstantDelaySchedule:
intervalDuration = s.Delay
}
if intervalDuration < minInterval {
return nil, zerrors.ThrowInvalidArgumentf(nil, "SERV-FJ12", "interval must be at least %s", minInterval)
}
logging.WithFields("interval", interval).Info("scheduling service ping")
return schedule, nil
}
// randomizeDaily generates a random time for the daily cron job
// to prevent all systems from sending the report at the same time.
func randomizeDaily() string {
minute := rand.Intn(60)
hour := rand.Intn(24)
return fmt.Sprintf("%d %d * * *", minute, hour)
}