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  }