storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/format-fs.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "math/rand" 24 "os" 25 "path" 26 "time" 27 28 "storj.io/minio/cmd/config" 29 "storj.io/minio/cmd/logger" 30 "storj.io/minio/pkg/lock" 31 ) 32 33 // FS format version strings. 34 const ( 35 formatBackendFS = "fs" 36 formatFSVersionV1 = "1" 37 formatFSVersionV2 = "2" 38 ) 39 40 // formatFSV1 - structure holds format version '1'. 41 type formatFSV1 struct { 42 formatMetaV1 43 FS struct { 44 Version string `json:"version"` 45 } `json:"fs"` 46 } 47 48 // formatFSV2 - structure is same as formatFSV1. But the multipart backend 49 // structure is flat instead of hierarchy now. 50 // In .minio.sys/multipart we have: 51 // sha256(bucket/object)/uploadID/[fs.json, 1.etag, 2.etag ....] 52 type formatFSV2 = formatFSV1 53 54 // Used to detect the version of "fs" format. 55 type formatFSVersionDetect struct { 56 FS struct { 57 Version string `json:"version"` 58 } `json:"fs"` 59 } 60 61 // Generic structure to manage both v1 and v2 structures 62 type formatFS struct { 63 formatMetaV1 64 FS interface{} `json:"fs"` 65 } 66 67 // Returns the latest "fs" format V1 68 func newFormatFSV1() (format *formatFSV1) { 69 f := &formatFSV1{} 70 f.Version = formatMetaVersionV1 71 f.Format = formatBackendFS 72 f.ID = mustGetUUID() 73 f.FS.Version = formatFSVersionV1 74 return f 75 } 76 77 // Returns the field formatMetaV1.Format i.e the string "fs" which is never likely to change. 78 // We do not use this function in Erasure to get the format as the file is not fcntl-locked on Erasure. 79 func formatMetaGetFormatBackendFS(r io.ReadSeeker) (string, error) { 80 format := &formatMetaV1{} 81 if err := jsonLoad(r, format); err != nil { 82 return "", err 83 } 84 if format.Version == formatMetaVersionV1 { 85 return format.Format, nil 86 } 87 return "", fmt.Errorf(`format.Version expected: %s, got: %s`, formatMetaVersionV1, format.Version) 88 } 89 90 // Returns formatFS.FS.Version 91 func formatFSGetVersion(r io.ReadSeeker) (string, error) { 92 format := &formatFSVersionDetect{} 93 if err := jsonLoad(r, format); err != nil { 94 return "", err 95 } 96 return format.FS.Version, nil 97 } 98 99 // Migrate from V1 to V2. V2 implements new backend format for multipart 100 // uploads. Delete the previous multipart directory. 101 func formatFSMigrateV1ToV2(ctx context.Context, wlk *lock.LockedFile, fsPath string) error { 102 version, err := formatFSGetVersion(wlk) 103 if err != nil { 104 return err 105 } 106 107 if version != formatFSVersionV1 { 108 return fmt.Errorf(`format.json version expected %s, found %s`, formatFSVersionV1, version) 109 } 110 111 if err = fsRemoveAll(ctx, path.Join(fsPath, minioMetaMultipartBucket)); err != nil { 112 return err 113 } 114 115 if err = os.MkdirAll(path.Join(fsPath, minioMetaMultipartBucket), 0755); err != nil { 116 return err 117 } 118 119 formatV1 := formatFSV1{} 120 if err = jsonLoad(wlk, &formatV1); err != nil { 121 return err 122 } 123 124 formatV2 := formatFSV2{} 125 formatV2.formatMetaV1 = formatV1.formatMetaV1 126 formatV2.FS.Version = formatFSVersionV2 127 128 return jsonSave(wlk.File, formatV2) 129 } 130 131 // Migrate the "fs" backend. 132 // Migration should happen when formatFSV1.FS.Version changes. This version 133 // can change when there is a change to the struct formatFSV1.FS or if there 134 // is any change in the backend file system tree structure. 135 func formatFSMigrate(ctx context.Context, wlk *lock.LockedFile, fsPath string) error { 136 // Add any migration code here in case we bump format.FS.Version 137 version, err := formatFSGetVersion(wlk) 138 if err != nil { 139 return err 140 } 141 142 switch version { 143 case formatFSVersionV1: 144 if err = formatFSMigrateV1ToV2(ctx, wlk, fsPath); err != nil { 145 return err 146 } 147 fallthrough 148 case formatFSVersionV2: 149 // We are at the latest version. 150 } 151 152 // Make sure that the version is what we expect after the migration. 153 version, err = formatFSGetVersion(wlk) 154 if err != nil { 155 return err 156 } 157 if version != formatFSVersionV2 { 158 return config.ErrUnexpectedBackendVersion(fmt.Errorf(`%s file: expected FS version: %s, found FS version: %s`, formatConfigFile, formatFSVersionV2, version)) 159 } 160 return nil 161 } 162 163 // Creates a new format.json if unformatted. 164 func createFormatFS(fsFormatPath string) error { 165 // Attempt a write lock on formatConfigFile `format.json` 166 // file stored in minioMetaBucket(.minio.sys) directory. 167 lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0600) 168 if err != nil { 169 return err 170 } 171 // Close the locked file upon return. 172 defer lk.Close() 173 174 fi, err := lk.Stat() 175 if err != nil { 176 return err 177 } 178 if fi.Size() != 0 { 179 // format.json already got created because of another minio process's createFormatFS() 180 return nil 181 } 182 183 return jsonSave(lk.File, newFormatFSV1()) 184 } 185 186 // This function returns a read-locked format.json reference to the caller. 187 // The file descriptor should be kept open throughout the life 188 // of the process so that another minio process does not try to 189 // migrate the backend when we are actively working on the backend. 190 func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, err error) { 191 fsFormatPath := pathJoin(fsPath, minioMetaBucket, formatConfigFile) 192 193 // Add a deployment ID, if it does not exist. 194 if err := formatFSFixDeploymentID(ctx, fsFormatPath); err != nil { 195 return nil, err 196 } 197 198 // Any read on format.json should be done with read-lock. 199 // Any write on format.json should be done with write-lock. 200 for { 201 isEmpty := false 202 rlk, err := lock.RLockedOpenFile(fsFormatPath) 203 if err == nil { 204 // format.json can be empty in a rare condition when another 205 // minio process just created the file but could not hold lock 206 // and write to it. 207 var fi os.FileInfo 208 fi, err = rlk.Stat() 209 if err != nil { 210 return nil, err 211 } 212 isEmpty = fi.Size() == 0 213 } 214 if osIsNotExist(err) || isEmpty { 215 if err == nil { 216 rlk.Close() 217 } 218 // Fresh disk - create format.json 219 err = createFormatFS(fsFormatPath) 220 if err == lock.ErrAlreadyLocked { 221 // Lock already present, sleep and attempt again. 222 // Can happen in a rare situation when a parallel minio process 223 // holds the lock and creates format.json 224 time.Sleep(100 * time.Millisecond) 225 continue 226 } 227 if err != nil { 228 return nil, err 229 } 230 // After successfully creating format.json try to hold a read-lock on 231 // the file. 232 continue 233 } 234 if err != nil { 235 return nil, err 236 } 237 238 formatBackend, err := formatMetaGetFormatBackendFS(rlk) 239 if err != nil { 240 return nil, err 241 } 242 if formatBackend != formatBackendFS { 243 return nil, fmt.Errorf(`%s file: expected format-type: %s, found: %s`, formatConfigFile, formatBackendFS, formatBackend) 244 } 245 version, err := formatFSGetVersion(rlk) 246 if err != nil { 247 return nil, err 248 } 249 if version != formatFSVersionV2 { 250 // Format needs migration 251 rlk.Close() 252 // Hold write lock during migration so that we do not disturb any 253 // minio processes running in parallel. 254 var wlk *lock.LockedFile 255 wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0) 256 if err == lock.ErrAlreadyLocked { 257 // Lock already present, sleep and attempt again. 258 time.Sleep(100 * time.Millisecond) 259 continue 260 } 261 if err != nil { 262 return nil, err 263 } 264 err = formatFSMigrate(ctx, wlk, fsPath) 265 wlk.Close() 266 if err != nil { 267 // Migration failed, bail out so that the user can observe what happened. 268 return nil, err 269 } 270 // Successfully migrated, now try to hold a read-lock on format.json 271 continue 272 } 273 var id string 274 if id, err = formatFSGetDeploymentID(rlk); err != nil { 275 rlk.Close() 276 return nil, err 277 } 278 globalDeploymentID = id 279 return rlk, nil 280 } 281 } 282 283 func formatFSGetDeploymentID(rlk *lock.RLockedFile) (id string, err error) { 284 format := &formatFS{} 285 if err := jsonLoad(rlk, format); err != nil { 286 return "", err 287 } 288 return format.ID, nil 289 } 290 291 // Generate a deployment ID if one does not exist already. 292 func formatFSFixDeploymentID(ctx context.Context, fsFormatPath string) error { 293 rlk, err := lock.RLockedOpenFile(fsFormatPath) 294 if err == nil { 295 // format.json can be empty in a rare condition when another 296 // minio process just created the file but could not hold lock 297 // and write to it. 298 var fi os.FileInfo 299 fi, err = rlk.Stat() 300 if err != nil { 301 rlk.Close() 302 return err 303 } 304 if fi.Size() == 0 { 305 rlk.Close() 306 return nil 307 } 308 } 309 if osIsNotExist(err) { 310 return nil 311 } 312 if err != nil { 313 return err 314 } 315 316 formatBackend, err := formatMetaGetFormatBackendFS(rlk) 317 if err != nil { 318 rlk.Close() 319 return err 320 } 321 if formatBackend != formatBackendFS { 322 rlk.Close() 323 return fmt.Errorf(`%s file: expected format-type: %s, found: %s`, formatConfigFile, formatBackendFS, formatBackend) 324 } 325 326 format := &formatFS{} 327 err = jsonLoad(rlk, format) 328 rlk.Close() 329 if err != nil { 330 return err 331 } 332 333 // Check if it needs to be updated 334 if format.ID != "" { 335 return nil 336 } 337 338 formatStartTime := time.Now().Round(time.Second) 339 getElapsedTime := func() string { 340 return time.Now().Round(time.Second).Sub(formatStartTime).String() 341 } 342 343 r := rand.New(rand.NewSource(time.Now().UnixNano())) 344 345 var wlk *lock.LockedFile 346 var stop bool 347 for !stop { 348 select { 349 case <-ctx.Done(): 350 return fmt.Errorf("Initializing FS format stopped gracefully") 351 default: 352 wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0) 353 if err == lock.ErrAlreadyLocked { 354 // Lock already present, sleep and attempt again 355 logger.Info("Another minio process(es) might be holding a lock to the file %s. Please kill that minio process(es) (elapsed %s)\n", fsFormatPath, getElapsedTime()) 356 time.Sleep(time.Duration(r.Float64() * float64(5*time.Second))) 357 continue 358 } 359 if err != nil { 360 return err 361 } 362 } 363 stop = true 364 } 365 defer wlk.Close() 366 367 if err = jsonLoad(wlk, format); err != nil { 368 return err 369 } 370 371 // Check if format needs to be updated 372 if format.ID != "" { 373 return nil 374 } 375 376 // Set new UUID to the format and save it 377 format.ID = mustGetUUID() 378 return jsonSave(wlk, format) 379 }