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 }