github.com/m3db/m3@v1.5.0/src/dbnode/persist/fs/snapshot_metadata_write.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package fs
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  
    27  	"github.com/m3db/m3/src/dbnode/digest"
    28  	"github.com/m3db/m3/src/dbnode/generated/proto/snapshot"
    29  	"github.com/m3db/m3/src/dbnode/persist"
    30  	xerrors "github.com/m3db/m3/src/x/errors"
    31  
    32  	"github.com/gogo/protobuf/proto"
    33  )
    34  
    35  type cleanupFn func() error
    36  
    37  // NewSnapshotMetadataWriter constructs a new snapshot metadata writer.
    38  func NewSnapshotMetadataWriter(opts Options) *SnapshotMetadataWriter {
    39  	return &SnapshotMetadataWriter{
    40  		opts: opts,
    41  
    42  		metadataFdWithDigest: digest.NewFdWithDigestWriter(opts.WriterBufferSize()),
    43  		digestBuf:            digest.NewBuffer(),
    44  	}
    45  }
    46  
    47  // SnapshotMetadataWriter is a writer for SnapshotMetadata.
    48  type SnapshotMetadataWriter struct {
    49  	opts Options
    50  
    51  	metadataFdWithDigest digest.FdWithDigestWriter
    52  	digestBuf            digest.Buffer
    53  }
    54  
    55  // SnapshotMetadataWriteArgs are the arguments for SnapshotMetadataWriter.Write.
    56  type SnapshotMetadataWriteArgs struct {
    57  	ID                  SnapshotMetadataIdentifier
    58  	CommitlogIdentifier persist.CommitLogFile
    59  }
    60  
    61  func (w *SnapshotMetadataWriter) Write(args SnapshotMetadataWriteArgs) (finalErr error) {
    62  	var (
    63  		prefix       = w.opts.FilePathPrefix()
    64  		snapshotsDir = SnapshotsDirPath(prefix)
    65  
    66  		// Set this up so that we can defer cleanup functions without ignoring
    67  		// the errors.
    68  		cleanupFns   = []cleanupFn{}
    69  		deferCleanup = func(f cleanupFn) {
    70  			cleanupFns = append(cleanupFns, f)
    71  		}
    72  	)
    73  	defer func() {
    74  		multiErr := xerrors.MultiError{}
    75  		if finalErr != nil {
    76  			multiErr = multiErr.Add(finalErr)
    77  		}
    78  
    79  		for _, f := range cleanupFns {
    80  			err := f()
    81  			if err != nil {
    82  				multiErr = multiErr.Add(err)
    83  			}
    84  		}
    85  
    86  		finalErr = multiErr.FinalError()
    87  	}()
    88  
    89  	if err := os.MkdirAll(snapshotsDir, w.opts.NewDirectoryMode()); err != nil {
    90  		return err
    91  	}
    92  	snapshotsDirFile, err := os.Open(snapshotsDir)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	deferCleanup(snapshotsDirFile.Close)
    97  
    98  	metadataPath := snapshotMetadataFilePathFromIdentifier(prefix, args.ID)
    99  	metadataFile, err := OpenWritable(metadataPath, w.opts.NewFileMode())
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	w.metadataFdWithDigest.Reset(metadataFile)
   105  	deferCleanup(w.metadataFdWithDigest.Close)
   106  
   107  	metadataBytes, err := proto.Marshal(&snapshot.Metadata{
   108  		SnapshotIndex: args.ID.Index,
   109  		SnapshotUUID:  []byte(args.ID.UUID.String()),
   110  		CommitlogID: &snapshot.CommitLogID{
   111  			FilePath: args.CommitlogIdentifier.FilePath,
   112  			Index:    args.CommitlogIdentifier.Index,
   113  		},
   114  	})
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	written, err := w.metadataFdWithDigest.Write(metadataBytes)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if written != len(metadataBytes) {
   124  		// Should never happen
   125  		return fmt.Errorf("did not complete writing metadata bytes, should have written: %d but wrote: %d bytes",
   126  			written, len(metadataBytes))
   127  	}
   128  
   129  	err = w.metadataFdWithDigest.Flush()
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	// Ensure the file is written out.
   135  	err = w.sync(metadataFile, snapshotsDirFile)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	checkpointPath := snapshotMetadataCheckpointFilePathFromIdentifier(prefix, args.ID)
   141  	checkpointFile, err := OpenWritable(checkpointPath, w.opts.NewFileMode())
   142  	if err != nil {
   143  		return err
   144  	}
   145  	deferCleanup(checkpointFile.Close)
   146  
   147  	// digestBuf does not need to be reset.
   148  	err = w.digestBuf.WriteDigestToFile(
   149  		checkpointFile, w.metadataFdWithDigest.Digest().Sum32())
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	// Ensure the file is written out.
   155  	return w.sync(checkpointFile, snapshotsDirFile)
   156  }
   157  
   158  // sync ensures that the provided file is persisted to disk by syncing it, as well
   159  // as its parent directory (ensuring the file is discoverable in the parent inode.)
   160  func (w *SnapshotMetadataWriter) sync(file, parent *os.File) error {
   161  	err := file.Sync()
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	return parent.Sync()
   167  }