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  }