Use live MQTT readiness for server health checks
This commit is contained in:
@@ -20,7 +20,7 @@ Open http://127.0.0.1:8080. If local port 8080 is occupied, use `-L 8081:127.0.0
|
|||||||
|
|
||||||
The runner configuration allows `/opt/test-mqtt-5/backend` in `container.valid_volumes`; only the deploy job requests that mount as `/runtime`. The prior runner configuration is backed up before this addition. Existing frontend job mounts remain unchanged. This workflow assumes that label continues to use the same server Docker daemon; moving the runner requires updating the deployment arrangement.
|
The runner configuration allows `/opt/test-mqtt-5/backend` in `container.valid_volumes`; only the deploy job requests that mount as `/runtime`. The prior runner configuration is backed up before this addition. Existing frontend job mounts remain unchanged. This workflow assumes that label continues to use the same server Docker daemon; moving the runner requires updating the deployment arrangement.
|
||||||
|
|
||||||
`deploy.sh` locks the runtime directory, validates Compose, preserves the previous image-tag file and Compose, deploys only backend, and checks container health for up to 60 seconds. Health requires the UI snapshot to report MQTT connected. Failed updates restore the previous release; a failed first deployment stops the backend. Images are retained for rollback. No host-wide image prune is run. Remote device return-path checks should still be performed after significant MQTT changes.
|
`deploy.sh` locks the runtime directory, validates Compose, preserves the previous image-tag file and Compose, deploys only backend, and checks container health for up to 60 seconds. Health requires `/healthz` to confirm current MQTT readiness, independent of bounded event-history eviction. Failed updates restore the previous release; a failed first deployment stops the backend. Images are retained for rollback. No host-wide image prune is run. Remote device return-path checks should still be performed after significant MQTT changes.
|
||||||
|
|
||||||
## Server operations
|
## Server operations
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ func Run(ctx context.Context, args []string, out, errOut io.Writer, sim bool) in
|
|||||||
var viewer *web.Viewer
|
var viewer *web.Viewer
|
||||||
if cfg.Web.Enabled {
|
if cfg.Web.Enabled {
|
||||||
viewer = web.New(cfg.Web, store, cfg.Deadlines.Shutdown)
|
viewer = web.New(cfg.Web, store, cfg.Deadlines.Shutdown)
|
||||||
|
viewer.SetReadiness(client.Ready)
|
||||||
if err = viewer.Start(); err != nil {
|
if err = viewer.Start(); err != nil {
|
||||||
fmt.Fprintln(errOut, err)
|
fmt.Fprintln(errOut, err)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type Snapshot struct {
|
|||||||
Compatibility []Compatibility `json:"compatibility"`
|
Compatibility []Compatibility `json:"compatibility"`
|
||||||
}
|
}
|
||||||
type Viewer struct {
|
type Viewer struct {
|
||||||
|
ready func() bool
|
||||||
cfg config.Web
|
cfg config.Web
|
||||||
store *events.Store
|
store *events.Store
|
||||||
shutdown time.Duration
|
shutdown time.Duration
|
||||||
@@ -60,10 +61,25 @@ func New(cfg config.Web, store *events.Store, shutdown time.Duration) *Viewer {
|
|||||||
mux.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(sub))))
|
mux.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(sub))))
|
||||||
mux.HandleFunc("GET /api/snapshot", v.snapshot)
|
mux.HandleFunc("GET /api/snapshot", v.snapshot)
|
||||||
mux.HandleFunc("GET /api/events", v.stream)
|
mux.HandleFunc("GET /api/events", v.stream)
|
||||||
|
mux.HandleFunc("GET /healthz", v.health)
|
||||||
v.handler = v.guard(mux)
|
v.handler = v.guard(mux)
|
||||||
v.server = &http.Server{Handler: v.handler, ReadHeaderTimeout: 3 * time.Second, IdleTimeout: 20 * time.Second, MaxHeaderBytes: 8 << 10}
|
v.server = &http.Server{Handler: v.handler, ReadHeaderTimeout: 3 * time.Second, IdleTimeout: 20 * time.Second, MaxHeaderBytes: 8 << 10}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetReadiness must be called before Start. Readiness reflects live MQTT state,
|
||||||
|
// independent of connection events rolling out of the bounded viewer history.
|
||||||
|
func (v *Viewer) SetReadiness(ready func() bool) { v.ready = ready }
|
||||||
|
|
||||||
|
func (v *Viewer) health(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
if v.ready == nil || !v.ready() {
|
||||||
|
http.Error(w, "MQTT not ready", http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"status":"ready"}`))
|
||||||
|
}
|
||||||
func (v *Viewer) Address() string {
|
func (v *Viewer) Address() string {
|
||||||
if v.listener == nil {
|
if v.listener == nil {
|
||||||
return v.cfg.Address
|
return v.cfg.Address
|
||||||
|
|||||||
@@ -162,3 +162,19 @@ func TestReportedScenariosDoNotClaimHardwareCompatibility(t *testing.T) {
|
|||||||
t.Fatal("scenario aliases retained history")
|
t.Fatal("scenario aliases retained history")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealthUsesCurrentMQTTReadiness(t *testing.T) {
|
||||||
|
v := testViewer()
|
||||||
|
ready := false
|
||||||
|
v.SetReadiness(func() bool { return ready })
|
||||||
|
for _, want := range []int{503, 200, 503} {
|
||||||
|
ready = want == 200
|
||||||
|
// No connection event retained: current readiness must still work.
|
||||||
|
r := httptest.NewRequest("GET", "http://127.0.0.1:8080/healthz", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
v.Handler().ServeHTTP(w, r)
|
||||||
|
if w.Code != want {
|
||||||
|
t.Fatalf("health status %d, want %d", w.Code, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ services:
|
|||||||
test:
|
test:
|
||||||
- CMD-SHELL
|
- CMD-SHELL
|
||||||
- >-
|
- >-
|
||||||
wget -qO- http://127.0.0.1:18080/api/snapshot | grep -q '"connection":"connected"'
|
wget -qO- http://127.0.0.1:18080/healthz >/dev/null
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 4s
|
timeout: 4s
|
||||||
retries: 6
|
retries: 6
|
||||||
|
|||||||
Reference in New Issue
Block a user