From 49da4ce41f70257d1e54e36350a9b10fd779cb38 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Wed, 28 Feb 2024 09:46:12 +0100 Subject: [PATCH 1/3] Update dskit Signed-off-by: Marco Pracucci --- go.mod | 2 +- go.sum | 4 +- .../ring/partition_instance_lifecycler.go | 15 +--- .../grafana/dskit/ring/partition_ring_http.go | 77 ++++++++++++++++--- .../dskit/ring/partition_ring_status.gohtml | 27 ++++++- vendor/modules.txt | 2 +- 6 files changed, 96 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 9602a516d18..2a7f3391d7a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/golang/snappy v0.0.4 github.com/google/gopacket v1.1.19 github.com/gorilla/mux v1.8.1 - github.com/grafana/dskit v0.0.0-20240223204243-057a4ceca444 + github.com/grafana/dskit v0.0.0-20240227143701-ffb15fd72381 github.com/grafana/e2e v0.1.2-0.20240118170847-db90b84177fc github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index a322c3b8908..a97436deba5 100644 --- a/go.sum +++ b/go.sum @@ -495,8 +495,8 @@ github.com/gosimple/slug v1.1.1 h1:fRu/digW+NMwBIP+RmviTK97Ho/bEj/C9swrCspN3D4= github.com/gosimple/slug v1.1.1/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0= github.com/grafana-tools/sdk v0.0.0-20220919052116-6562121319fc h1:PXZQA2WCxe85Tnn+WEvr8fDpfwibmEPgfgFEaC87G24= github.com/grafana-tools/sdk v0.0.0-20220919052116-6562121319fc/go.mod h1:AHHlOEv1+GGQ3ktHMlhuTUwo3zljV3QJbC0+8o2kn+4= -github.com/grafana/dskit v0.0.0-20240223204243-057a4ceca444 h1:/I7hHPr4SogVxPVrGyrm7PT3XYzOJ/h6ar5RT7E0j+0= -github.com/grafana/dskit v0.0.0-20240223204243-057a4ceca444/go.mod h1:4po0PqmfyD9VcxOzvcx34XL0G6sQP6KlQdFdGiys4jE= +github.com/grafana/dskit v0.0.0-20240227143701-ffb15fd72381 h1:nz5NkbEOnZ30z2oEZW9kMqsc+FsgGN4dwzTFEzGy6nc= +github.com/grafana/dskit v0.0.0-20240227143701-ffb15fd72381/go.mod h1:4po0PqmfyD9VcxOzvcx34XL0G6sQP6KlQdFdGiys4jE= github.com/grafana/e2e v0.1.2-0.20240118170847-db90b84177fc h1:BW+LjKJDz0So5LI8UZfW5neWeKpSkWqhmGjQFzcFfLM= github.com/grafana/e2e v0.1.2-0.20240118170847-db90b84177fc/go.mod h1:JVmqPBe8A/pZWwRoJW5ZjyALeY5OXMzPl7LrVXOdZAI= github.com/grafana/goautoneg v0.0.0-20231010094147-47ce5e72a9ae h1:Yxbw9jKGJVC6qAK5Ubzzb/qZwM6rRMMqaDc/d4Vp3pM= diff --git a/vendor/github.com/grafana/dskit/ring/partition_instance_lifecycler.go b/vendor/github.com/grafana/dskit/ring/partition_instance_lifecycler.go index f0c425ad249..9ad31a54f26 100644 --- a/vendor/github.com/grafana/dskit/ring/partition_instance_lifecycler.go +++ b/vendor/github.com/grafana/dskit/ring/partition_instance_lifecycler.go @@ -154,20 +154,7 @@ func (l *PartitionInstanceLifecycler) GetPartitionState(ctx context.Context) (Pa func (l *PartitionInstanceLifecycler) ChangePartitionState(ctx context.Context, toState PartitionState) error { return l.runOnLifecyclerLoop(func() error { err := l.updateRing(ctx, func(ring *PartitionRingDesc) (bool, error) { - partition, exists := ring.Partitions[l.cfg.PartitionID] - if !exists { - return false, ErrPartitionDoesNotExist - } - - if partition.State == toState { - return false, nil - } - - if !isPartitionStateChangeAllowed(partition.State, toState) { - return false, errors.Wrapf(ErrPartitionStateChangeNotAllowed, "change partition state from %s to %s", partition.State.CleanName(), toState.CleanName()) - } - - return ring.UpdatePartitionState(l.cfg.PartitionID, toState, time.Now()), nil + return changePartitionState(ring, l.cfg.PartitionID, toState) }) if err != nil { diff --git a/vendor/github.com/grafana/dskit/ring/partition_ring_http.go b/vendor/github.com/grafana/dskit/ring/partition_ring_http.go index 76df254484f..8e58c58c7af 100644 --- a/vendor/github.com/grafana/dskit/ring/partition_ring_http.go +++ b/vendor/github.com/grafana/dskit/ring/partition_ring_http.go @@ -1,10 +1,13 @@ package ring import ( + "context" _ "embed" + "fmt" "html/template" "net/http" "sort" + "strconv" "time" "golang.org/x/exp/slices" @@ -21,19 +24,36 @@ var partitionRingPageTemplate = template.Must(template.New("webpage").Funcs(temp }, }).Parse(partitionRingPageContent)) +type PartitionRingUpdater interface { + ChangePartitionState(ctx context.Context, partitionID int32, toState PartitionState) error +} + type PartitionRingPageHandler struct { - ring PartitionRingReader + reader PartitionRingReader + updater PartitionRingUpdater } -func NewPartitionRingPageHandler(ring PartitionRingReader) *PartitionRingPageHandler { +func NewPartitionRingPageHandler(reader PartitionRingReader, updater PartitionRingUpdater) *PartitionRingPageHandler { return &PartitionRingPageHandler{ - ring: ring, + reader: reader, + updater: updater, } } func (h *PartitionRingPageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + h.handleGetRequest(w, req) + case http.MethodPost: + h.handlePostRequest(w, req) + default: + http.Error(w, "Unsupported HTTP method", http.StatusMethodNotAllowed) + } +} + +func (h *PartitionRingPageHandler) handleGetRequest(w http.ResponseWriter, req *http.Request) { var ( - ring = h.ring.PartitionRing() + ring = h.reader.PartitionRing() ringDesc = ring.desc ) @@ -45,7 +65,8 @@ func (h *PartitionRingPageHandler) ServeHTTP(w http.ResponseWriter, req *http.Re partitionsByID[id] = partitionPageData{ ID: id, - State: partition.State.CleanName(), + Corrupted: false, + State: partition.State, StateTimestamp: partition.GetStateTime(), OwnerIDs: owners, } @@ -59,7 +80,8 @@ func (h *PartitionRingPageHandler) ServeHTTP(w http.ResponseWriter, req *http.Re if !exists { partition = partitionPageData{ ID: owner.OwnedPartition, - State: "Corrupt", + Corrupted: true, + State: PartitionUnknown, StateTimestamp: time.Time{}, OwnerIDs: []string{ownerID}, } @@ -86,16 +108,51 @@ func (h *PartitionRingPageHandler) ServeHTTP(w http.ResponseWriter, req *http.Re renderHTTPResponse(w, partitionRingPageData{ Partitions: partitions, + PartitionStateChanges: map[PartitionState]PartitionState{ + PartitionPending: PartitionActive, + PartitionActive: PartitionInactive, + PartitionInactive: PartitionActive, + }, }, partitionRingPageTemplate, req) } +func (h *PartitionRingPageHandler) handlePostRequest(w http.ResponseWriter, req *http.Request) { + if req.FormValue("action") == "change_state" { + partitionID, err := strconv.Atoi(req.FormValue("partition_id")) + if err != nil { + http.Error(w, fmt.Sprintf("invalid partition ID: %s", err.Error()), http.StatusBadRequest) + return + } + + toState, ok := PartitionState_value[req.FormValue("partition_state")] + if !ok { + http.Error(w, "invalid partition state", http.StatusBadRequest) + return + } + + if err := h.updater.ChangePartitionState(req.Context(), int32(partitionID), PartitionState(toState)); err != nil { + http.Error(w, fmt.Sprintf("failed to change partition state: %s", err.Error()), http.StatusBadRequest) + return + } + } + + // Implement PRG pattern to prevent double-POST and work with CSRF middleware. + // https://en.wikipedia.org/wiki/Post/Redirect/Get + w.Header().Set("Location", "#") + w.WriteHeader(http.StatusFound) +} + type partitionRingPageData struct { Partitions []partitionPageData `json:"partitions"` + + // PartitionStateChanges maps the allowed state changes through the UI. + PartitionStateChanges map[PartitionState]PartitionState `json:"-"` } type partitionPageData struct { - ID int32 `json:"id"` - State string `json:"state"` - StateTimestamp time.Time `json:"state_timestamp"` - OwnerIDs []string `json:"owner_ids"` + ID int32 `json:"id"` + Corrupted bool `json:"corrupted"` + State PartitionState `json:"state"` + StateTimestamp time.Time `json:"state_timestamp"` + OwnerIDs []string `json:"owner_ids"` } diff --git a/vendor/github.com/grafana/dskit/ring/partition_ring_status.gohtml b/vendor/github.com/grafana/dskit/ring/partition_ring_status.gohtml index 1afcd91f115..f4f9afe87d8 100644 --- a/vendor/github.com/grafana/dskit/ring/partition_ring_status.gohtml +++ b/vendor/github.com/grafana/dskit/ring/partition_ring_status.gohtml @@ -1,4 +1,4 @@ -{{- /*gotype: github.com/grafana/dskit/ring.httpResponse */ -}} +{{- /*gotype: github.com/grafana/dskit/ring.partitionRingPageData */ -}} @@ -15,13 +15,21 @@ State State updated at Owners + Actions + {{ $stateChanges := .PartitionStateChanges }} {{ range $partition := .Partitions }} - + {{ .ID }} - {{ .State }} + + {{ if .Corrupted }} + Corrupt + {{ else }} + {{ .State.CleanName }} + {{ end }} + {{ if not .StateTimestamp.IsZero }} {{ .StateTimestamp | formatTimestamp }} @@ -34,6 +42,19 @@ {{$ownerID}}
{{ end }} + + + {{ if and (not .Corrupted) (ne (index $stateChanges .State) 0) }} + {{ $toState := index $stateChanges .State }} +
+ + + + + +
+ {{ end }} + {{ end }} diff --git a/vendor/modules.txt b/vendor/modules.txt index 85579d0d9bf..92b7482b91c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -538,7 +538,7 @@ github.com/gosimple/slug # github.com/grafana-tools/sdk v0.0.0-20220919052116-6562121319fc ## explicit; go 1.13 github.com/grafana-tools/sdk -# github.com/grafana/dskit v0.0.0-20240223204243-057a4ceca444 +# github.com/grafana/dskit v0.0.0-20240227143701-ffb15fd72381 ## explicit; go 1.20 github.com/grafana/dskit/backoff github.com/grafana/dskit/ballast From abda8597c292f84431e3ba8a399328d11f8b577b Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Wed, 28 Feb 2024 09:57:21 +0100 Subject: [PATCH 2/3] Commit missing file Signed-off-by: Marco Pracucci --- .../dskit/ring/partition_ring_editor.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 vendor/github.com/grafana/dskit/ring/partition_ring_editor.go diff --git a/vendor/github.com/grafana/dskit/ring/partition_ring_editor.go b/vendor/github.com/grafana/dskit/ring/partition_ring_editor.go new file mode 100644 index 00000000000..a816693e55c --- /dev/null +++ b/vendor/github.com/grafana/dskit/ring/partition_ring_editor.go @@ -0,0 +1,64 @@ +package ring + +import ( + "context" + "time" + + "github.com/pkg/errors" + + "github.com/grafana/dskit/kv" +) + +// PartitionRingEditor is standalone component that can be used to modify the partitions ring. +// If you want to implement the partition lifecycle you should use PartitionInstanceLifecycler instead. +type PartitionRingEditor struct { + ringKey string + store kv.Client +} + +func NewPartitionRingEditor(ringKey string, store kv.Client) *PartitionRingEditor { + return &PartitionRingEditor{ + ringKey: ringKey, + store: store, + } +} + +// ChangePartitionState changes the partition state to toState. +// This function returns ErrPartitionDoesNotExist if the partition doesn't exist, +// and ErrPartitionStateChangeNotAllowed if the state change is not allowed. +func (l *PartitionRingEditor) ChangePartitionState(ctx context.Context, partitionID int32, toState PartitionState) error { + return l.updateRing(ctx, func(ring *PartitionRingDesc) (bool, error) { + return changePartitionState(ring, partitionID, toState) + }) +} + +func (l *PartitionRingEditor) updateRing(ctx context.Context, update func(ring *PartitionRingDesc) (bool, error)) error { + return l.store.CAS(ctx, l.ringKey, func(in interface{}) (out interface{}, retry bool, err error) { + ringDesc := GetOrCreatePartitionRingDesc(in) + + if changed, err := update(ringDesc); err != nil { + return nil, false, err + } else if !changed { + return nil, false, nil + } + + return ringDesc, true, nil + }) +} + +func changePartitionState(ring *PartitionRingDesc, partitionID int32, toState PartitionState) (changed bool, _ error) { + partition, exists := ring.Partitions[partitionID] + if !exists { + return false, ErrPartitionDoesNotExist + } + + if partition.State == toState { + return false, nil + } + + if !isPartitionStateChangeAllowed(partition.State, toState) { + return false, errors.Wrapf(ErrPartitionStateChangeNotAllowed, "change partition state from %s to %s", partition.State.CleanName(), toState.CleanName()) + } + + return ring.UpdatePartitionState(partitionID, toState, time.Now()), nil +} From 6c25c0db64333a4ec2b14b4effc33236855bb023 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Wed, 28 Feb 2024 10:02:49 +0100 Subject: [PATCH 3/3] Fix compilation issue Signed-off-by: Marco Pracucci --- pkg/mimir/modules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mimir/modules.go b/pkg/mimir/modules.go index f24cd26ecfa..a3354c10d70 100644 --- a/pkg/mimir/modules.go +++ b/pkg/mimir/modules.go @@ -371,7 +371,7 @@ func (t *Mimir) initIngesterPartitionRing() (services.Service, error) { t.IngesterPartitionInstanceRing = ring.NewPartitionInstanceRing(t.IngesterPartitionRingWatcher, t.IngesterRing, t.Cfg.Ingester.IngesterRing.HeartbeatTimeout) // Expose a web page to view the partitions ring state. - t.API.RegisterIngesterPartitionRing(ring.NewPartitionRingPageHandler(t.IngesterPartitionRingWatcher)) + t.API.RegisterIngesterPartitionRing(ring.NewPartitionRingPageHandler(t.IngesterPartitionRingWatcher, ring.NewPartitionRingEditor(ingester.PartitionRingKey, kvClient))) return t.IngesterPartitionRingWatcher, nil }