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
76 lines
1.7 KiB
Go
76 lines
1.7 KiB
Go
package factory
|
|
|
|
import "sync"
|
|
|
|
// State represents the lifecycle state of a service.
|
|
type State struct {
|
|
s string
|
|
}
|
|
|
|
func (s State) String() string {
|
|
return s.s
|
|
}
|
|
|
|
// MarshalText implements encoding.TextMarshaler so State can be used as a JSON map key.
|
|
func (s State) MarshalText() ([]byte, error) {
|
|
return []byte(s.s), nil
|
|
}
|
|
|
|
var (
|
|
StateStarting = State{"starting"}
|
|
StateRunning = State{"running"}
|
|
StateFailed = State{"failed"}
|
|
)
|
|
|
|
// serviceWithState wraps a NamedService with thread-safe state tracking.
|
|
type serviceWithState struct {
|
|
// service is the underlying named service.
|
|
service NamedService
|
|
|
|
// dependsOn is the validated subset of declared dependencies that exist in the registry.
|
|
dependsOn []Name
|
|
|
|
// mu protects state and startErr from concurrent access.
|
|
mu sync.RWMutex
|
|
|
|
// state is the current lifecycle state of the service.
|
|
state State
|
|
|
|
// healthyC is closed when the service transitions to StateRunning.
|
|
healthyC chan struct{}
|
|
|
|
// startReturnedC is closed when Start() returns, whether with nil or an error.
|
|
startReturnedC chan struct{}
|
|
|
|
// startErr is the error returned by Start(), or nil if it returned successfully.
|
|
startErr error
|
|
}
|
|
|
|
func newServiceWithState(service NamedService) *serviceWithState {
|
|
return &serviceWithState{
|
|
service: service,
|
|
state: StateStarting,
|
|
healthyC: make(chan struct{}),
|
|
startReturnedC: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (ss *serviceWithState) setState(state State) {
|
|
ss.mu.Lock()
|
|
defer ss.mu.Unlock()
|
|
ss.state = state
|
|
if state == StateRunning {
|
|
select {
|
|
case <-ss.healthyC:
|
|
default:
|
|
close(ss.healthyC)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ss *serviceWithState) getState() State {
|
|
ss.mu.RLock()
|
|
defer ss.mu.RUnlock()
|
|
return ss.state
|
|
}
|