github.com/thanos-io/thanos@v0.32.5/pkg/verifier/safe_delete.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package verifier 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "path/filepath" 11 12 "github.com/go-kit/log" 13 "github.com/go-kit/log/level" 14 "github.com/oklog/ulid" 15 "github.com/pkg/errors" 16 17 "github.com/thanos-io/objstore" 18 19 "github.com/thanos-io/thanos/pkg/block" 20 "github.com/thanos-io/thanos/pkg/block/metadata" 21 ) 22 23 // TSDBBlockExistsInBucket checks to see if a given TSDB block ID exists in a 24 // bucket. If so, true is returned. An error is returned on failure and in 25 // such case the boolean result has no meaning. 26 func TSDBBlockExistsInBucket(ctx context.Context, bkt objstore.Bucket, id ulid.ULID) (bool, error) { 27 foundDir := false 28 err := bkt.Iter(ctx, id.String(), func(name string) error { 29 foundDir = true 30 return nil 31 }) 32 33 return foundDir, err 34 } 35 36 // BackupAndDelete moves a TSDB block to a backup bucket and on success removes 37 // it from the source bucket. If deleteDelay is zero, block is removed from source bucket. 38 // else the block is marked for deletion. 39 // It returns error if block dir already exists in 40 // the backup bucket (blocks should be immutable) or if any of the operations 41 // fail. 42 func BackupAndDelete(ctx Context, id ulid.ULID) error { 43 // Does this TSDB block exist in backupBkt already? 44 found, err := TSDBBlockExistsInBucket(ctx, ctx.BackupBkt, id) 45 if err != nil { 46 return err 47 } 48 if found { 49 return errors.Errorf("%s dir seems to exists in backup bucket. Remove this block manually if you are sure it is safe to do", id) 50 } 51 52 // Create a tempdir to locally store TSDB block. 53 tempdir, err := os.MkdirTemp("", fmt.Sprintf("safe-delete-%s", id.String())) 54 if err != nil { 55 return err 56 } 57 defer func() { 58 if err := os.RemoveAll(tempdir); err != nil { 59 level.Warn(ctx.Logger).Log("msg", "failed to delete dir", "dir", tempdir, "err", err) 60 } 61 }() 62 63 // Download the TSDB block. 64 dir := filepath.Join(tempdir, id.String()) 65 if err := block.Download(ctx, ctx.Logger, ctx.Bkt, id, dir); err != nil { 66 return errors.Wrap(err, "download from source") 67 } 68 69 // Backup the block. 70 if err := backupDownloaded(ctx, ctx.Logger, dir, ctx.BackupBkt, id); err != nil { 71 return err 72 } 73 74 // Block uploaded, so we are ok to remove from src bucket. 75 if ctx.DeleteDelay.Seconds() == 0 { 76 level.Info(ctx.Logger).Log("msg", "Deleting block", "id", id.String()) 77 if err := block.Delete(ctx, ctx.Logger, ctx.Bkt, id); err != nil { 78 return errors.Wrap(err, "delete from source") 79 } 80 } 81 82 level.Info(ctx.Logger).Log("msg", "Marking block as deleted", "id", id.String()) 83 if err := block.MarkForDeletion(ctx, ctx.Logger, ctx.Bkt, id, "manual verify-repair", ctx.metrics.blocksMarkedForDeletion); err != nil { 84 return errors.Wrap(err, "marking delete from source") 85 } 86 return nil 87 } 88 89 // BackupAndDeleteDownloaded works much like BackupAndDelete in that it will 90 // move a TSDB block from a bucket to a backup bucket. If deleteDelay param is zero, block is removed from source bucket. 91 // else the block is marked for deletion. The bdir parameter 92 // points to the location on disk where the TSDB block was previously 93 // downloaded allowing this function to avoid downloading the TSDB block from 94 // the source bucket again. An error is returned if any operation fails. 95 func BackupAndDeleteDownloaded(ctx Context, bdir string, id ulid.ULID) error { 96 // Does this TSDB block exist in backupBkt already? 97 found, err := TSDBBlockExistsInBucket(ctx, ctx.BackupBkt, id) 98 if err != nil { 99 return err 100 } 101 if found { 102 return errors.Errorf("%s dir seems to exists in backup bucket. Remove this block manually if you are sure it is safe to do", id) 103 } 104 105 // Backup the block. 106 if err := backupDownloaded(ctx, ctx.Logger, bdir, ctx.BackupBkt, id); err != nil { 107 return err 108 } 109 110 // Block uploaded, so we are ok to remove from src bucket. 111 if ctx.DeleteDelay.Seconds() == 0 { 112 level.Info(ctx.Logger).Log("msg", "Deleting block", "id", id.String()) 113 if err := block.Delete(ctx, ctx.Logger, ctx.Bkt, id); err != nil { 114 return errors.Wrap(err, "delete from source") 115 } 116 return nil 117 } 118 119 level.Info(ctx.Logger).Log("msg", "Marking block as deleted", "id", id.String()) 120 if err := block.MarkForDeletion(ctx, ctx.Logger, ctx.Bkt, id, "manual verify-repair", ctx.metrics.blocksMarkedForDeletion); err != nil { 121 return errors.Wrap(err, "marking delete from source") 122 } 123 return nil 124 } 125 126 // backupDownloaded is a helper function that uploads a TSDB block 127 // found on disk to the given bucket. An error is returned if any operation 128 // fails. 129 func backupDownloaded(ctx context.Context, logger log.Logger, bdir string, backupBkt objstore.Bucket, id ulid.ULID) error { 130 // Safety checks. 131 if _, err := os.Stat(filepath.Join(bdir, "meta.json")); err != nil { 132 // If there is any error stat'ing meta.json inside the TSDB block 133 // then declare the existing block as bad and refuse to upload it. 134 // TODO: Make this check more robust. 135 return errors.Wrap(err, "existing tsdb block is invalid") 136 } 137 138 // Upload the on disk TSDB block. 139 level.Info(logger).Log("msg", "Uploading block to backup bucket", "id", id.String()) 140 if err := block.Upload(ctx, logger, backupBkt, bdir, metadata.NoneFunc); err != nil { 141 return errors.Wrap(err, "upload to backup") 142 } 143 144 return nil 145 }