Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s
- reconnect safely after token rotation and retry leased results - reject malformed tasks and remove production cluster debug mutation - validate environment files and require immutable container images BREAKING CHANGE: Docker install, deploy, and Compose now require an immutable repository@sha256 image reference.
80 строки
2.9 KiB
Go
80 строки
2.9 KiB
Go
package webapp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
)
|
|
|
|
// clusterStatusResponse is the JSON the operator-facing
|
|
// /web/api/cluster/status endpoint returns. The shape is stable so the
|
|
// e2e shell script and any future frontend pages can pin against it.
|
|
//
|
|
// FSMConfigVersion / FSMOutboxLen / FSMPartition surface the FSM-side
|
|
// operator signals from plan section 6.1 (config_version, outbox
|
|
// length, partition_state) so a single GET tells the operator what
|
|
// config the cluster has adopted, whether the notification outbox is
|
|
// draining, and whether the cluster sees itself as partitioned.
|
|
type clusterStatusResponse struct {
|
|
SelfID string `json:"self_id"`
|
|
Role string `json:"role"`
|
|
Term uint64 `json:"term"`
|
|
LeaderID string `json:"leader_id"`
|
|
Voters []string `json:"voters"`
|
|
AppliedIndex uint64 `json:"applied_index"`
|
|
CommitIndex uint64 `json:"commit_index"`
|
|
FSMChecks int `json:"fsm_checks"`
|
|
FSMMembers int `json:"fsm_membership"`
|
|
FSMConfigVersion uint64 `json:"fsm_config_version"`
|
|
FSMOutboxLen int `json:"fsm_outbox_len"`
|
|
FSMPartition string `json:"fsm_partition"`
|
|
ClusterID string `json:"cluster_id"`
|
|
LocalAddr string `json:"local_addr"`
|
|
}
|
|
|
|
// handleClusterStatus serializes the current cluster state for the
|
|
// operator. Returns 503 if no cluster is attached; 200 otherwise.
|
|
//
|
|
// Admin-only: the worker webapp is single-tenant so the session
|
|
// middleware (requireSession) is the admin check. Cross-tenant
|
|
// protection is not required at this layer.
|
|
//
|
|
// The `r` parameter is unused but kept so the signature matches
|
|
// http.HandlerFunc (the route is registered via requireSession).
|
|
func (s *Server) handleClusterStatus(w http.ResponseWriter, _ *http.Request) {
|
|
writeNoStore(w)
|
|
if s.cluster == nil {
|
|
http.Error(w, "cluster not configured", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
stats := s.cluster.Stats()
|
|
resp := clusterStatusResponse{
|
|
SelfID: stats.NodeID,
|
|
Role: stats.State,
|
|
Term: stats.Term,
|
|
LeaderID: stats.Leader,
|
|
Voters: stats.Voters,
|
|
AppliedIndex: stats.AppliedIndex,
|
|
CommitIndex: stats.LastIndex,
|
|
FSMChecks: stats.FSMChecks,
|
|
FSMMembers: stats.FSMMembers,
|
|
FSMConfigVersion: stats.FSMConfigVersion,
|
|
FSMOutboxLen: stats.FSMOutboxLen,
|
|
FSMPartition: stats.FSMPartition,
|
|
ClusterID: s.cluster.ClusterID(),
|
|
LocalAddr: s.cluster.LocalAddr(),
|
|
}
|
|
if resp.Voters == nil {
|
|
resp.Voters = []string{}
|
|
}
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
|
s.deps.Logger.Printf("cluster status encode: %v", err)
|
|
}
|
|
}
|
|
|
|
// ErrClusterNotConfigured is returned when a cluster-admin endpoint is
|
|
// hit on a server without a cluster attached.
|
|
var ErrClusterNotConfigured = errors.New("webapp: cluster not configured")
|