github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/chunk/purger/tenant_deletion_api.go (about)

     1  package purger
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/oklog/ulid"
    12  	"github.com/pkg/errors"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/thanos-io/thanos/pkg/objstore"
    15  
    16  	"github.com/cortexproject/cortex/pkg/storage/bucket"
    17  	cortex_tsdb "github.com/cortexproject/cortex/pkg/storage/tsdb"
    18  	"github.com/cortexproject/cortex/pkg/tenant"
    19  	"github.com/cortexproject/cortex/pkg/util"
    20  )
    21  
    22  type TenantDeletionAPI struct {
    23  	bucketClient objstore.Bucket
    24  	logger       log.Logger
    25  	cfgProvider  bucket.TenantConfigProvider
    26  }
    27  
    28  func NewTenantDeletionAPI(storageCfg cortex_tsdb.BlocksStorageConfig, cfgProvider bucket.TenantConfigProvider, logger log.Logger, reg prometheus.Registerer) (*TenantDeletionAPI, error) {
    29  	bucketClient, err := createBucketClient(storageCfg, logger, reg)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	return newTenantDeletionAPI(bucketClient, cfgProvider, logger), nil
    35  }
    36  
    37  func newTenantDeletionAPI(bkt objstore.Bucket, cfgProvider bucket.TenantConfigProvider, logger log.Logger) *TenantDeletionAPI {
    38  	return &TenantDeletionAPI{
    39  		bucketClient: bkt,
    40  		cfgProvider:  cfgProvider,
    41  		logger:       logger,
    42  	}
    43  }
    44  
    45  func (api *TenantDeletionAPI) DeleteTenant(w http.ResponseWriter, r *http.Request) {
    46  	ctx := r.Context()
    47  	userID, err := tenant.TenantID(ctx)
    48  	if err != nil {
    49  		// When Cortex is running, it uses Auth Middleware for checking X-Scope-OrgID and injecting tenant into context.
    50  		// Auth Middleware sends http.StatusUnauthorized if X-Scope-OrgID is missing, so we do too here, for consistency.
    51  		http.Error(w, err.Error(), http.StatusUnauthorized)
    52  		return
    53  	}
    54  
    55  	err = cortex_tsdb.WriteTenantDeletionMark(r.Context(), api.bucketClient, userID, api.cfgProvider, cortex_tsdb.NewTenantDeletionMark(time.Now()))
    56  	if err != nil {
    57  		level.Error(api.logger).Log("msg", "failed to write tenant deletion mark", "user", userID, "err", err)
    58  
    59  		http.Error(w, err.Error(), http.StatusInternalServerError)
    60  		return
    61  	}
    62  
    63  	level.Info(api.logger).Log("msg", "tenant deletion mark in blocks storage created", "user", userID)
    64  
    65  	w.WriteHeader(http.StatusOK)
    66  }
    67  
    68  type DeleteTenantStatusResponse struct {
    69  	TenantID      string `json:"tenant_id"`
    70  	BlocksDeleted bool   `json:"blocks_deleted"`
    71  }
    72  
    73  func (api *TenantDeletionAPI) DeleteTenantStatus(w http.ResponseWriter, r *http.Request) {
    74  	ctx := r.Context()
    75  	userID, err := tenant.TenantID(ctx)
    76  	if err != nil {
    77  		http.Error(w, err.Error(), http.StatusBadRequest)
    78  		return
    79  	}
    80  
    81  	result := DeleteTenantStatusResponse{}
    82  	result.TenantID = userID
    83  	result.BlocksDeleted, err = api.isBlocksForUserDeleted(ctx, userID)
    84  	if err != nil {
    85  		http.Error(w, err.Error(), http.StatusInternalServerError)
    86  		return
    87  	}
    88  
    89  	util.WriteJSONResponse(w, result)
    90  }
    91  
    92  func (api *TenantDeletionAPI) isBlocksForUserDeleted(ctx context.Context, userID string) (bool, error) {
    93  	var errBlockFound = errors.New("block found")
    94  
    95  	userBucket := bucket.NewUserBucketClient(userID, api.bucketClient, api.cfgProvider)
    96  	err := userBucket.Iter(ctx, "", func(s string) error {
    97  		s = strings.TrimSuffix(s, "/")
    98  
    99  		_, err := ulid.Parse(s)
   100  		if err != nil {
   101  			// not block, keep looking
   102  			return nil
   103  		}
   104  
   105  		// Used as shortcut to stop iteration.
   106  		return errBlockFound
   107  	})
   108  
   109  	if errors.Is(err, errBlockFound) {
   110  		return false, nil
   111  	}
   112  
   113  	if err != nil {
   114  		return false, err
   115  	}
   116  
   117  	// No blocks found, all good.
   118  	return true, nil
   119  }
   120  
   121  func createBucketClient(cfg cortex_tsdb.BlocksStorageConfig, logger log.Logger, reg prometheus.Registerer) (objstore.Bucket, error) {
   122  	bucketClient, err := bucket.NewClient(context.Background(), cfg.Bucket, "purger", logger, reg)
   123  	if err != nil {
   124  		return nil, errors.Wrap(err, "create bucket client")
   125  	}
   126  
   127  	return bucketClient, nil
   128  }