github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/tools/thanosconvert/thanosconvert.go (about)

     1  package thanosconvert
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/pkg/errors"
    12  	"github.com/thanos-io/thanos/pkg/block"
    13  	"github.com/thanos-io/thanos/pkg/block/metadata"
    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  )
    19  
    20  // ThanosBlockConverter converts blocks written by Thanos to make them readable by Cortex
    21  type ThanosBlockConverter struct {
    22  	logger log.Logger
    23  	bkt    objstore.Bucket
    24  	dryRun bool
    25  }
    26  
    27  type PerUserResults struct {
    28  	FailedBlocks, ConvertedBlocks, UnchangedBlocks []string
    29  }
    30  
    31  func (r *PerUserResults) AddFailed(blockID string) {
    32  	r.FailedBlocks = append(r.FailedBlocks, blockID)
    33  }
    34  func (r *PerUserResults) AddConverted(blockID string) {
    35  	r.ConvertedBlocks = append(r.ConvertedBlocks, blockID)
    36  }
    37  
    38  func (r *PerUserResults) AddUnchanged(blockID string) {
    39  	r.UnchangedBlocks = append(r.UnchangedBlocks, blockID)
    40  }
    41  
    42  type Results map[string]PerUserResults
    43  
    44  // NewThanosBlockConverter creates a ThanosBlockConverter
    45  func NewThanosBlockConverter(ctx context.Context, cfg bucket.Config, dryRun bool, logger log.Logger) (*ThanosBlockConverter, error) {
    46  	bkt, err := bucket.NewClient(ctx, cfg, "thanosconvert", logger, nil)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	return &ThanosBlockConverter{
    52  		bkt:    bkt,
    53  		logger: logger,
    54  		dryRun: dryRun,
    55  	}, err
    56  }
    57  
    58  // Run iterates over all blocks from all users in a bucket
    59  func (c ThanosBlockConverter) Run(ctx context.Context) (Results, error) {
    60  	results := make(Results)
    61  
    62  	scanner := cortex_tsdb.NewUsersScanner(c.bkt, cortex_tsdb.AllUsers, c.logger)
    63  	users, _, err := scanner.ScanUsers(ctx)
    64  	if err != nil {
    65  		return results, errors.Wrap(err, "error while scanning users")
    66  	}
    67  	level.Info(c.logger).Log("msg", "Scanned users")
    68  
    69  	for _, u := range users {
    70  		r, err := c.convertUser(ctx, u)
    71  		results[u] = r
    72  		if err != nil {
    73  			return results, errors.Wrap(err, fmt.Sprintf("error converting user %s", u))
    74  		}
    75  	}
    76  	return results, nil
    77  }
    78  
    79  func (c ThanosBlockConverter) convertUser(ctx context.Context, user string) (PerUserResults, error) {
    80  	results := PerUserResults{}
    81  
    82  	// No per-tenant config provider because the thanosconvert tool doesn't support it.
    83  	userBucketClient := bucket.NewUserBucketClient(user, c.bkt, nil)
    84  
    85  	err := userBucketClient.Iter(ctx, "", func(o string) error {
    86  		blockID, ok := block.IsBlockDir(o)
    87  		if !ok {
    88  			// not a block
    89  			return nil
    90  		}
    91  
    92  		// retrieve meta.json
    93  
    94  		meta, err := block.DownloadMeta(ctx, c.logger, userBucketClient, blockID)
    95  		if err != nil {
    96  			level.Error(c.logger).Log("msg", "download block meta", "block", blockID.String(), "user", user, "err", err.Error())
    97  			results.AddFailed(blockID.String())
    98  			return nil
    99  		}
   100  
   101  		// convert and upload if appropriate
   102  
   103  		newMeta, changesRequired := convertMetadata(meta, user)
   104  
   105  		if len(changesRequired) > 0 {
   106  			if c.dryRun {
   107  				level.Info(c.logger).Log("msg", "Block requires changes (dry-run)", "block", blockID.String(), "user", user, "changes_required", strings.Join(changesRequired, ","))
   108  			} else {
   109  				level.Info(c.logger).Log("msg", "Block requires changes, uploading new meta.json", "block", blockID.String(), "user", user, "changes_required", strings.Join(changesRequired, ","))
   110  				if err := c.uploadNewMeta(ctx, userBucketClient, blockID.String(), newMeta); err != nil {
   111  					level.Error(c.logger).Log("msg", "Update meta.json", "block", blockID.String(), "user", user, "err", err.Error())
   112  					results.AddFailed(blockID.String())
   113  					return nil
   114  				}
   115  			}
   116  			results.AddConverted(blockID.String())
   117  		} else {
   118  			level.Info(c.logger).Log("msg", "Block doesn't need changes", "block", blockID.String(), "user", user)
   119  			results.AddUnchanged(blockID.String())
   120  		}
   121  
   122  		return nil
   123  	})
   124  	if err != nil {
   125  		return results, err
   126  	}
   127  
   128  	return results, nil
   129  }
   130  
   131  func (c *ThanosBlockConverter) uploadNewMeta(ctx context.Context, userBucketClient objstore.Bucket, blockID string, meta metadata.Meta) error {
   132  	var body bytes.Buffer
   133  	if err := meta.Write(&body); err != nil {
   134  		return errors.Wrap(err, "encode json")
   135  	}
   136  
   137  	if err := userBucketClient.Upload(ctx, fmt.Sprintf("%s/meta.json", blockID), &body); err != nil {
   138  		return errors.Wrap(err, "upload to bucket")
   139  	}
   140  	return nil
   141  }
   142  
   143  func convertMetadata(meta metadata.Meta, expectedUser string) (metadata.Meta, []string) {
   144  	var changesRequired []string
   145  
   146  	org, ok := meta.Thanos.Labels[cortex_tsdb.TenantIDExternalLabel]
   147  	if !ok {
   148  		changesRequired = append(changesRequired, "add __org_id__ label")
   149  	} else if org != expectedUser {
   150  		changesRequired = append(changesRequired, fmt.Sprintf("change __org_id__ from %s to %s", org, expectedUser))
   151  	}
   152  
   153  	// remove __org_id__ so that we can see if there are any other labels
   154  	delete(meta.Thanos.Labels, cortex_tsdb.TenantIDExternalLabel)
   155  	if len(meta.Thanos.Labels) > 0 {
   156  		changesRequired = append(changesRequired, "remove extra Thanos labels")
   157  	}
   158  
   159  	meta.Thanos.Labels = map[string]string{
   160  		cortex_tsdb.TenantIDExternalLabel: expectedUser,
   161  	}
   162  
   163  	return meta, changesRequired
   164  }