mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
* feat(factory): add service state tracking, AwaitHealthy, depends_on, and /healthz endpoint Add explicit lifecycle state tracking to factory.Registry services (starting/running/failed) modeled after Guava's ServiceManager. Services can declare dependencies via NewNamedService(..., dependsOn) which are validated for unknown refs and cycles at registry creation. AwaitHealthy blocks until all services reach running state. A /healthz endpoint is wired through signozapiserver returning 200/503 with per-service state. * feat(apiserver): move health endpoints to /api/v2/ and register readyz, livez * refactor(factory): use gonum for cycle detection, return error on cycles, fix test assertions Replace custom DFS cycle detection with gonum's topo.Sort + TarjanSCC. Dependency cycles now return an error from NewRegistry instead of being silently dropped. Use assert for final test assertions and require only for intermediate setup errors. * chore: go mod tidy * refactor(factory): decouple Handler from Registry, wire through Handlers struct Move Handler implementation to a private handler struct with NewHandler constructor instead of methods on *Registry. Route handler through the existing Handlers struct as RegistryHandler. Rename healthz.go to registry.go in signozapiserver. Fix handler_test.go for new param. * feat(factory): add ServiceWithHealthy interface, add Healthy to authz, user depends on authz Add ServiceWithHealthy interface embedding Service + Healthy. NamedService now delegates Healthy() to the underlying service, eliminating unwrapService. AuthZ interface requires Healthy(), implemented in both pkg and ee providers. User service declares dependency on authz via dependsOn. * test(integration): use /api/v2/healthz for readiness check, log 503 response body * fix(factory): replace fmt.Errorf with errors.Newf in tests to satisfy linter * feat: generate openapi spec * fix(integration): log errors at error level in healthz readiness check * test(integration): log and assert healthz response in test_setup * feat(user): implement ServiceWithHealthy for user service User service signals healthy after successful root user reconciliation or immediately when disabled. User Service interface now embeds factory.ServiceWithHealthy. * fix(factory): reflect service names as strings * fix(apiserver): document health 503 responses * feat: generate openapi spec
83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
package factory
|
|
|
|
import "context"
|
|
|
|
// Service is the core lifecycle interface for long-running services.
|
|
type Service interface {
|
|
// Starts a service. It should block and should not return until the service is stopped or it fails.
|
|
Start(context.Context) error
|
|
|
|
// Stops a service.
|
|
Stop(context.Context) error
|
|
}
|
|
|
|
// Healthy is an optional interface that services can implement to signal
|
|
// when they have completed startup and are ready to serve.
|
|
// Services that do not implement this interface are considered healthy
|
|
// immediately after Start() is called.
|
|
type Healthy interface {
|
|
// Healthy returns a channel that is closed when the service is healthy.
|
|
Healthy() <-chan struct{}
|
|
}
|
|
|
|
// ServiceWithHealthy is a Service that explicitly signals when it is healthy.
|
|
type ServiceWithHealthy interface {
|
|
Service
|
|
Healthy
|
|
}
|
|
|
|
// NamedService is a Service with a Name and optional dependencies.
|
|
type NamedService interface {
|
|
Named
|
|
ServiceWithHealthy
|
|
// DependsOn returns the names of services that must be healthy before this service starts.
|
|
DependsOn() []Name
|
|
}
|
|
|
|
// closedC is a pre-closed channel returned for services that don't implement Healthy.
|
|
var closedC = func() chan struct{} {
|
|
c := make(chan struct{})
|
|
close(c)
|
|
return c
|
|
}()
|
|
|
|
type namedService struct {
|
|
name Name
|
|
dependsOn []Name
|
|
service Service
|
|
}
|
|
|
|
// NewNamedService wraps a Service with a Name and optional dependency names.
|
|
func NewNamedService(name Name, service Service, dependsOn ...Name) NamedService {
|
|
return &namedService{
|
|
name: name,
|
|
dependsOn: dependsOn,
|
|
service: service,
|
|
}
|
|
}
|
|
|
|
func (s *namedService) Name() Name {
|
|
return s.name
|
|
}
|
|
|
|
func (s *namedService) DependsOn() []Name {
|
|
return s.dependsOn
|
|
}
|
|
|
|
func (s *namedService) Start(ctx context.Context) error {
|
|
return s.service.Start(ctx)
|
|
}
|
|
|
|
func (s *namedService) Stop(ctx context.Context) error {
|
|
return s.service.Stop(ctx)
|
|
}
|
|
|
|
// Healthy delegates to the underlying service if it implements Healthy,
|
|
// otherwise returns an already-closed channel (immediately healthy).
|
|
func (s *namedService) Healthy() <-chan struct{} {
|
|
if h, ok := s.service.(Healthy); ok {
|
|
return h.Healthy()
|
|
}
|
|
return closedC
|
|
}
|