github.com/thanos-io/thanos@v0.32.5/pkg/api/blocks/v1.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package v1 5 6 import ( 7 "net/http" 8 "time" 9 10 "github.com/go-kit/log" 11 "github.com/oklog/ulid" 12 "github.com/opentracing/opentracing-go" 13 "github.com/pkg/errors" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/prometheus/client_golang/prometheus/promauto" 16 "github.com/prometheus/common/route" 17 "github.com/thanos-io/objstore" 18 19 "github.com/thanos-io/thanos/pkg/api" 20 "github.com/thanos-io/thanos/pkg/block" 21 "github.com/thanos-io/thanos/pkg/block/metadata" 22 extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http" 23 "github.com/thanos-io/thanos/pkg/logging" 24 ) 25 26 // BlocksAPI is a very simple API used by Thanos Block Viewer. 27 type BlocksAPI struct { 28 baseAPI *api.BaseAPI 29 logger log.Logger 30 globalBlocksInfo *BlocksInfo 31 loadedBlocksInfo *BlocksInfo 32 disableCORS bool 33 bkt objstore.Bucket 34 } 35 36 type BlocksInfo struct { 37 Label string `json:"label"` 38 Blocks []metadata.Meta `json:"blocks"` 39 RefreshedAt time.Time `json:"refreshedAt"` 40 Err error `json:"err"` 41 } 42 43 type ActionType int32 44 45 const ( 46 Deletion ActionType = iota 47 NoCompaction 48 Unknown 49 ) 50 51 func parse(s string) ActionType { 52 switch s { 53 case "DELETION": 54 return Deletion 55 case "NO_COMPACTION": 56 return NoCompaction 57 default: 58 return Unknown 59 } 60 } 61 62 // NewBlocksAPI creates a simple API to be used by Thanos Block Viewer. 63 func NewBlocksAPI(logger log.Logger, disableCORS bool, label string, flagsMap map[string]string, bkt objstore.Bucket) *BlocksAPI { 64 return &BlocksAPI{ 65 baseAPI: api.NewBaseAPI(logger, disableCORS, flagsMap), 66 logger: logger, 67 globalBlocksInfo: &BlocksInfo{ 68 Blocks: []metadata.Meta{}, 69 Label: label, 70 }, 71 loadedBlocksInfo: &BlocksInfo{ 72 Blocks: []metadata.Meta{}, 73 Label: label, 74 }, 75 disableCORS: disableCORS, 76 bkt: bkt, 77 } 78 } 79 80 func (bapi *BlocksAPI) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger, ins extpromhttp.InstrumentationMiddleware, logMiddleware *logging.HTTPServerMiddleware) { 81 bapi.baseAPI.Register(r, tracer, logger, ins, logMiddleware) 82 83 instr := api.GetInstr(tracer, logger, ins, logMiddleware, bapi.disableCORS) 84 85 r.Get("/blocks", instr("blocks", bapi.blocks)) 86 r.Post("/blocks/mark", instr("blocks_mark", bapi.markBlock)) 87 } 88 89 func (bapi *BlocksAPI) markBlock(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 90 idParam := r.FormValue("id") 91 actionParam := r.FormValue("action") 92 detailParam := r.FormValue("detail") 93 94 if idParam == "" { 95 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("ID cannot be empty")}, func() {} 96 } 97 98 if actionParam == "" { 99 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("Action cannot be empty")}, func() {} 100 } 101 102 id, err := ulid.Parse(idParam) 103 if err != nil { 104 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("ULID %q is not valid: %v", idParam, err)}, func() {} 105 } 106 107 actionType := parse(actionParam) 108 switch actionType { 109 case Deletion: 110 err := block.MarkForDeletion(r.Context(), bapi.logger, bapi.bkt, id, detailParam, promauto.With(nil).NewCounter(prometheus.CounterOpts{})) 111 if err != nil { 112 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 113 } 114 case NoCompaction: 115 err := block.MarkForNoCompact(r.Context(), bapi.logger, bapi.bkt, id, metadata.ManualNoCompactReason, detailParam, promauto.With(nil).NewCounter(prometheus.CounterOpts{})) 116 if err != nil { 117 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {} 118 } 119 default: 120 return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("not supported marker %v", actionParam)}, func() {} 121 } 122 return nil, nil, nil, func() {} 123 } 124 125 func (bapi *BlocksAPI) blocks(r *http.Request) (interface{}, []error, *api.ApiError, func()) { 126 viewParam := r.URL.Query().Get("view") 127 if viewParam == "loaded" { 128 return bapi.loadedBlocksInfo, nil, nil, func() {} 129 } 130 return bapi.globalBlocksInfo, nil, nil, func() {} 131 } 132 133 func (b *BlocksInfo) set(blocks []metadata.Meta, err error) { 134 if err != nil { 135 // Last view is maintained. 136 b.RefreshedAt = time.Now() 137 b.Err = err 138 return 139 } 140 141 b.RefreshedAt = time.Now() 142 b.Blocks = blocks 143 b.Err = err 144 } 145 146 // SetGlobal updates the global blocks' metadata in the API. 147 func (bapi *BlocksAPI) SetGlobal(blocks []metadata.Meta, err error) { 148 bapi.globalBlocksInfo.set(blocks, err) 149 } 150 151 // SetLoaded updates the local blocks' metadata in the API. 152 func (bapi *BlocksAPI) SetLoaded(blocks []metadata.Meta, err error) { 153 bapi.loadedBlocksInfo.set(blocks, err) 154 }