github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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 }