mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
219 lines
8.0 KiB
Go
219 lines
8.0 KiB
Go
package livestore
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/grafana/dskit/ring"
|
|
"github.com/grafana/dskit/services"
|
|
"github.com/grafana/tempo/pkg/util"
|
|
"github.com/grafana/tempo/pkg/util/shutdownmarker"
|
|
)
|
|
|
|
type PreparePartitionDownscaleResponse struct {
|
|
Timestamp int64 `json:"timestamp"`
|
|
State string `json:"state"`
|
|
}
|
|
|
|
// PreparePartitionDownscaleHandler prepares the live-store's partition downscaling. The partition owned by the
|
|
// live-store will switch to INACTIVE state (read-only).
|
|
//
|
|
// Following methods are supported:
|
|
//
|
|
// - GET
|
|
// Returns timestamp when partition was switched to INACTIVE state, or 0, if partition is not in INACTIVE state.
|
|
//
|
|
// - POST
|
|
// Switches the partition to INACTIVE state (if not yet), and returns the timestamp when the switch to
|
|
// INACTIVE state happened.
|
|
//
|
|
// - DELETE
|
|
// Sets partition back from INACTIVE to ACTIVE state.
|
|
func (s *LiveStore) PreparePartitionDownscaleHandler(w http.ResponseWriter, r *http.Request) {
|
|
logger := log.With(s.logger, "partition", s.ingestPartitionID)
|
|
|
|
// Don't allow callers to change the shutdown configuration while we're in the middle
|
|
// of starting or shutting down.
|
|
if s.State() != services.Running {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
if s.ingestPartitionLifecycler == nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodPost:
|
|
// It's not allowed to prepare the downscale while in PENDING state. Why? Because if the downscale
|
|
// will be later cancelled, we don't know if it was requested in PENDING or ACTIVE state, so we
|
|
// don't know to which state reverting back. Given a partition is expected to stay in PENDING state
|
|
// for a short period, we simply don't allow this case.
|
|
state, _, err := s.ingestPartitionLifecycler.GetPartitionState(r.Context())
|
|
if err != nil {
|
|
level.Error(logger).Log("msg", "cannot downscale: failed to check partition state in the ring", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if state == ring.PartitionPending {
|
|
level.Warn(logger).Log("msg", "received a request to prepare partition for shutdown, but the request can't be satisfied because the partition is in PENDING state")
|
|
w.WriteHeader(http.StatusConflict)
|
|
return
|
|
}
|
|
if state == ring.PartitionInactive {
|
|
level.Debug(logger).Log("msg", "partition is already set to INACTIVE state")
|
|
break
|
|
}
|
|
|
|
if err := s.ingestPartitionLifecycler.ChangePartitionState(r.Context(), ring.PartitionInactive); err != nil {
|
|
level.Error(logger).Log("msg", "failed to change partition state to inactive", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
level.Info(logger).Log("msg", "partition prepared for downscaling")
|
|
|
|
case http.MethodDelete:
|
|
state, _, err := s.ingestPartitionLifecycler.GetPartitionState(r.Context())
|
|
if err != nil {
|
|
level.Error(logger).Log("msg", "cannot cancel downscaling: failed to check partition state in the ring", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// If partition is inactive, make it active. We ignore other states Active and especially Pending.
|
|
if state == ring.PartitionInactive {
|
|
// We don't switch it back to PENDING state if there are not enough owners because we want to guarantee consistency
|
|
// in the read path. If the partition is within the lookback period we need to guarantee that partition will be queried.
|
|
// Moving back to PENDING will cause us loosing consistency, because PENDING partitions are not queried by design.
|
|
// We could move back to PENDING if there are not enough owners and the partition moved to INACTIVE more than
|
|
// "lookback period" ago, but since we delete inactive partitions with no owners that moved to inactive since longer
|
|
// than "lookback period" ago, it looks to be an edge case not worth to address.
|
|
if err := s.ingestPartitionLifecycler.ChangePartitionState(r.Context(), ring.PartitionActive); err != nil {
|
|
level.Error(logger).Log("msg", "failed to change partition state to active", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
level.Info(logger).Log("msg", "partition downscaling preparation cancelled")
|
|
} else {
|
|
level.Debug(logger).Log("msg", "partition is not in INACTIVE state, so no need to cancel downscaling", "state", state.String())
|
|
}
|
|
case http.MethodGet:
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
state, stateTimestamp, err := s.ingestPartitionLifecycler.GetPartitionState(r.Context())
|
|
if err != nil {
|
|
level.Error(logger).Log("msg", "failed to check partition state in the ring", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if state == ring.PartitionInactive {
|
|
util.WriteJSONResponse(w, PreparePartitionDownscaleResponse{
|
|
Timestamp: stateTimestamp.Unix(),
|
|
State: state.String(),
|
|
})
|
|
} else {
|
|
util.WriteJSONResponse(w, PreparePartitionDownscaleResponse{
|
|
Timestamp: 0,
|
|
State: state.String(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// PrepareDownscaleHandler prepares the live-store for downscale by removing it from the ring on shutdown.
|
|
//
|
|
// Following methods are supported:
|
|
//
|
|
// - GET
|
|
// Returns set if the live-store is prepared for downscale, unset otherwise.
|
|
//
|
|
// - POST
|
|
// Enables prepare downscale mode (remove the live-store from the ring on shutdown).
|
|
//
|
|
// - DELETE
|
|
// Disables prepare downscale mode (do not remove the live-store from the ring on shutdown).
|
|
func (s *LiveStore) PrepareDownscaleHandler(w http.ResponseWriter, r *http.Request) {
|
|
// Don't allow callers to change the shutdown configuration while we're in the middle
|
|
// of starting or shutting down.
|
|
if s.State() != services.Running {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
if s.ingestPartitionLifecycler == nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
shutdownMarkerPath := shutdownmarker.GetPath(s.cfg.ShutdownMarkerDir)
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
exists, err := shutdownmarker.Exists(shutdownMarkerPath)
|
|
if err != nil {
|
|
level.Error(s.logger).Log("msg", "unable to check for prepare-shutdown marker file", "path", shutdownMarkerPath, "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if exists {
|
|
util.WriteTextResponse(w, "set\n")
|
|
} else {
|
|
util.WriteTextResponse(w, "unset\n")
|
|
}
|
|
case http.MethodPost:
|
|
if err := shutdownmarker.Create(shutdownMarkerPath); err != nil {
|
|
level.Error(s.logger).Log("msg", "unable to create prepare-shutdown marker file", "path", shutdownMarkerPath, "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
state, _, err := s.ingestPartitionLifecycler.GetPartitionState(r.Context())
|
|
if err != nil {
|
|
level.Error(s.logger).Log("msg", "failed to get partition state", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if state != ring.PartitionInactive {
|
|
level.Info(s.logger).Log("msg", "partition must be in INACTIVE state")
|
|
w.WriteHeader(http.StatusConflict)
|
|
return
|
|
}
|
|
s.setPrepareShutdown()
|
|
level.Info(s.logger).Log("msg", "live-store prepared for shutdown")
|
|
|
|
case http.MethodDelete:
|
|
if err := shutdownmarker.Remove(shutdownMarkerPath); err != nil {
|
|
level.Error(s.logger).Log("msg", "unable to remove prepare-shutdown marker file", "path", shutdownMarkerPath, "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.unsetPrepareShutdown()
|
|
level.Info(s.logger).Log("msg", "live-store shutdown preparation cancelled")
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// setPrepareShutdown configures the live-store for shutdown preparation
|
|
func (s *LiveStore) setPrepareShutdown() {
|
|
s.ingestPartitionLifecycler.SetRemoveOwnerOnShutdown(true)
|
|
s.ingestPartitionLifecycler.SetCreatePartitionOnStartup(false)
|
|
}
|
|
|
|
// unsetPrepareShutdown reverts the shutdown preparation configuration
|
|
func (s *LiveStore) unsetPrepareShutdown() {
|
|
s.ingestPartitionLifecycler.SetRemoveOwnerOnShutdown(false)
|
|
s.ingestPartitionLifecycler.SetCreatePartitionOnStartup(true)
|
|
}
|