2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2018-01-08 17:30:55 -05:00
package cmd
import (
2018-04-05 18:04:40 -04:00
"context"
2018-01-08 17:30:55 -05:00
"fmt"
"io"
2020-11-02 20:52:13 -05:00
"math/rand"
2018-01-08 17:30:55 -05:00
"os"
2018-01-31 16:17:24 -05:00
"path"
2018-01-08 17:30:55 -05:00
"time"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/lock"
"github.com/minio/minio/internal/logger"
2018-01-08 17:30:55 -05:00
)
// FS format version strings.
const (
formatBackendFS = "fs"
formatFSVersionV1 = "1"
2018-01-31 16:17:24 -05:00
formatFSVersionV2 = "2"
2018-01-08 17:30:55 -05:00
)
// formatFSV1 - structure holds format version '1'.
type formatFSV1 struct {
formatMetaV1
FS struct {
Version string ` json:"version" `
} ` json:"fs" `
}
2018-01-31 16:17:24 -05:00
// formatFSV2 - structure is same as formatFSV1. But the multipart backend
// structure is flat instead of hierarchy now.
// In .minio.sys/multipart we have:
// sha256(bucket/object)/uploadID/[fs.json, 1.etag, 2.etag ....]
type formatFSV2 = formatFSV1
2018-01-08 17:30:55 -05:00
// Used to detect the version of "fs" format.
type formatFSVersionDetect struct {
FS struct {
Version string ` json:"version" `
} ` json:"fs" `
}
2018-07-18 23:17:35 -04:00
// Generic structure to manage both v1 and v2 structures
type formatFS struct {
formatMetaV1
FS interface { } ` json:"fs" `
}
2018-01-31 16:17:24 -05:00
// Returns the latest "fs" format V1
2018-01-08 17:30:55 -05:00
func newFormatFSV1 ( ) ( format * formatFSV1 ) {
f := & formatFSV1 { }
f . Version = formatMetaVersionV1
f . Format = formatBackendFS
2018-07-18 23:17:35 -04:00
f . ID = mustGetUUID ( )
2018-01-08 17:30:55 -05:00
f . FS . Version = formatFSVersionV1
return f
}
// Returns the field formatMetaV1.Format i.e the string "fs" which is never likely to change.
2020-06-12 23:04:01 -04:00
// We do not use this function in Erasure to get the format as the file is not fcntl-locked on Erasure.
2018-01-08 17:30:55 -05:00
func formatMetaGetFormatBackendFS ( r io . ReadSeeker ) ( string , error ) {
format := & formatMetaV1 { }
2018-02-06 18:37:48 -05:00
if err := jsonLoad ( r , format ) ; err != nil {
2018-01-08 17:30:55 -05:00
return "" , err
}
if format . Version == formatMetaVersionV1 {
return format . Format , nil
}
return "" , fmt . Errorf ( ` format.Version expected: %s, got: %s ` , formatMetaVersionV1 , format . Version )
}
// Returns formatFS.FS.Version
func formatFSGetVersion ( r io . ReadSeeker ) ( string , error ) {
format := & formatFSVersionDetect { }
2018-02-06 18:37:48 -05:00
if err := jsonLoad ( r , format ) ; err != nil {
2018-01-08 17:30:55 -05:00
return "" , err
}
return format . FS . Version , nil
}
2018-01-31 16:17:24 -05:00
// Migrate from V1 to V2. V2 implements new backend format for multipart
// uploads. Delete the previous multipart directory.
2018-04-05 18:04:40 -04:00
func formatFSMigrateV1ToV2 ( ctx context . Context , wlk * lock . LockedFile , fsPath string ) error {
2018-01-31 16:17:24 -05:00
version , err := formatFSGetVersion ( wlk )
if err != nil {
return err
}
if version != formatFSVersionV1 {
return fmt . Errorf ( ` format.json version expected %s, found %s ` , formatFSVersionV1 , version )
}
2018-04-05 18:04:40 -04:00
if err = fsRemoveAll ( ctx , path . Join ( fsPath , minioMetaMultipartBucket ) ) ; err != nil {
2018-01-31 16:17:24 -05:00
return err
}
2022-01-02 12:15:06 -05:00
if err = os . MkdirAll ( path . Join ( fsPath , minioMetaMultipartBucket ) , 0 o755 ) ; err != nil {
2018-01-31 16:17:24 -05:00
return err
}
2018-07-18 23:17:35 -04:00
formatV1 := formatFSV1 { }
if err = jsonLoad ( wlk , & formatV1 ) ; err != nil {
return err
}
formatV2 := formatFSV2 { }
formatV2 . formatMetaV1 = formatV1 . formatMetaV1
formatV2 . FS . Version = formatFSVersionV2
return jsonSave ( wlk . File , formatV2 )
2018-01-31 16:17:24 -05:00
}
2018-01-08 17:30:55 -05:00
// Migrate the "fs" backend.
// Migration should happen when formatFSV1.FS.Version changes. This version
// can change when there is a change to the struct formatFSV1.FS or if there
// is any change in the backend file system tree structure.
2018-04-05 18:04:40 -04:00
func formatFSMigrate ( ctx context . Context , wlk * lock . LockedFile , fsPath string ) error {
2018-01-08 17:30:55 -05:00
// Add any migration code here in case we bump format.FS.Version
2018-01-31 16:17:24 -05:00
version , err := formatFSGetVersion ( wlk )
if err != nil {
return err
}
switch version {
case formatFSVersionV1 :
2018-04-05 18:04:40 -04:00
if err = formatFSMigrateV1ToV2 ( ctx , wlk , fsPath ) ; err != nil {
2018-01-31 16:17:24 -05:00
return err
}
fallthrough
case formatFSVersionV2 :
// We are at the latest version.
}
2018-01-08 17:30:55 -05:00
// Make sure that the version is what we expect after the migration.
2018-01-31 16:17:24 -05:00
version , err = formatFSGetVersion ( wlk )
2018-01-08 17:30:55 -05:00
if err != nil {
return err
}
2018-01-31 16:17:24 -05:00
if version != formatFSVersionV2 {
2019-10-04 13:35:33 -04:00
return config . ErrUnexpectedBackendVersion ( fmt . Errorf ( ` %s file: expected FS version: %s, found FS version: %s ` , formatConfigFile , formatFSVersionV2 , version ) )
2018-01-08 17:30:55 -05:00
}
return nil
}
// Creates a new format.json if unformatted.
2019-10-15 21:35:41 -04:00
func createFormatFS ( fsFormatPath string ) error {
2018-01-08 17:30:55 -05:00
// Attempt a write lock on formatConfigFile `format.json`
// file stored in minioMetaBucket(.minio.sys) directory.
2022-01-02 12:15:06 -05:00
lk , err := lock . TryLockedOpenFile ( fsFormatPath , os . O_RDWR | os . O_CREATE , 0 o600 )
2018-01-08 17:30:55 -05:00
if err != nil {
2018-04-05 18:04:40 -04:00
return err
2018-01-08 17:30:55 -05:00
}
// Close the locked file upon return.
defer lk . Close ( )
fi , err := lk . Stat ( )
if err != nil {
2018-04-05 18:04:40 -04:00
return err
2018-01-08 17:30:55 -05:00
}
if fi . Size ( ) != 0 {
// format.json already got created because of another minio process's createFormatFS()
return nil
}
2018-02-06 18:37:48 -05:00
return jsonSave ( lk . File , newFormatFSV1 ( ) )
2018-01-08 17:30:55 -05:00
}
// This function returns a read-locked format.json reference to the caller.
// The file descriptor should be kept open throughout the life
// of the process so that another minio process does not try to
// migrate the backend when we are actively working on the backend.
2018-04-05 18:04:40 -04:00
func initFormatFS ( ctx context . Context , fsPath string ) ( rlk * lock . RLockedFile , err error ) {
2018-01-08 17:30:55 -05:00
fsFormatPath := pathJoin ( fsPath , minioMetaBucket , formatConfigFile )
2018-07-18 23:17:35 -04:00
// Add a deployment ID, if it does not exist.
2020-05-04 23:04:06 -04:00
if err := formatFSFixDeploymentID ( ctx , fsFormatPath ) ; err != nil {
2018-07-18 23:17:35 -04:00
return nil , err
}
2018-01-08 17:30:55 -05:00
// Any read on format.json should be done with read-lock.
// Any write on format.json should be done with write-lock.
for {
isEmpty := false
rlk , err := lock . RLockedOpenFile ( fsFormatPath )
if err == nil {
// format.json can be empty in a rare condition when another
// minio process just created the file but could not hold lock
// and write to it.
var fi os . FileInfo
fi , err = rlk . Stat ( )
if err != nil {
2018-04-05 18:04:40 -04:00
return nil , err
2018-01-08 17:30:55 -05:00
}
isEmpty = fi . Size ( ) == 0
}
2020-11-23 11:36:49 -05:00
if osIsNotExist ( err ) || isEmpty {
2018-01-08 17:30:55 -05:00
if err == nil {
rlk . Close ( )
}
// Fresh disk - create format.json
2019-10-15 21:35:41 -04:00
err = createFormatFS ( fsFormatPath )
2018-01-08 17:30:55 -05:00
if err == lock . ErrAlreadyLocked {
// Lock already present, sleep and attempt again.
// Can happen in a rare situation when a parallel minio process
// holds the lock and creates format.json
time . Sleep ( 100 * time . Millisecond )
continue
}
if err != nil {
2018-04-05 18:04:40 -04:00
return nil , err
2018-01-08 17:30:55 -05:00
}
// After successfully creating format.json try to hold a read-lock on
// the file.
continue
}
if err != nil {
2018-04-05 18:04:40 -04:00
return nil , err
2018-01-08 17:30:55 -05:00
}
formatBackend , err := formatMetaGetFormatBackendFS ( rlk )
if err != nil {
2018-04-05 18:04:40 -04:00
return nil , err
2018-01-08 17:30:55 -05:00
}
2022-05-30 13:58:37 -04:00
if formatBackend == formatBackendErasureSingle {
return nil , errFreshDisk
}
2018-01-08 17:30:55 -05:00
if formatBackend != formatBackendFS {
return nil , fmt . Errorf ( ` %s file: expected format-type: %s, found: %s ` , formatConfigFile , formatBackendFS , formatBackend )
}
version , err := formatFSGetVersion ( rlk )
if err != nil {
return nil , err
}
2018-01-31 16:17:24 -05:00
if version != formatFSVersionV2 {
2018-01-08 17:30:55 -05:00
// Format needs migration
rlk . Close ( )
// Hold write lock during migration so that we do not disturb any
// minio processes running in parallel.
2018-07-18 23:17:35 -04:00
var wlk * lock . LockedFile
wlk , err = lock . TryLockedOpenFile ( fsFormatPath , os . O_RDWR , 0 )
2018-01-08 17:30:55 -05:00
if err == lock . ErrAlreadyLocked {
// Lock already present, sleep and attempt again.
time . Sleep ( 100 * time . Millisecond )
continue
}
if err != nil {
return nil , err
}
2018-04-05 18:04:40 -04:00
err = formatFSMigrate ( ctx , wlk , fsPath )
2018-01-08 17:30:55 -05:00
wlk . Close ( )
if err != nil {
// Migration failed, bail out so that the user can observe what happened.
return nil , err
}
// Successfully migrated, now try to hold a read-lock on format.json
continue
}
2018-07-18 23:17:35 -04:00
var id string
if id , err = formatFSGetDeploymentID ( rlk ) ; err != nil {
rlk . Close ( )
return nil , err
}
2018-11-19 17:47:03 -05:00
globalDeploymentID = id
2018-01-08 17:30:55 -05:00
return rlk , nil
}
}
2018-07-18 23:17:35 -04:00
func formatFSGetDeploymentID ( rlk * lock . RLockedFile ) ( id string , err error ) {
format := & formatFS { }
if err := jsonLoad ( rlk , format ) ; err != nil {
return "" , err
}
return format . ID , nil
}
// Generate a deployment ID if one does not exist already.
2020-05-04 23:04:06 -04:00
func formatFSFixDeploymentID ( ctx context . Context , fsFormatPath string ) error {
2018-07-18 23:17:35 -04:00
rlk , err := lock . RLockedOpenFile ( fsFormatPath )
if err == nil {
// format.json can be empty in a rare condition when another
// minio process just created the file but could not hold lock
// and write to it.
var fi os . FileInfo
fi , err = rlk . Stat ( )
if err != nil {
rlk . Close ( )
return err
}
if fi . Size ( ) == 0 {
rlk . Close ( )
return nil
}
}
2020-11-23 11:36:49 -05:00
if osIsNotExist ( err ) {
2018-07-18 23:17:35 -04:00
return nil
}
if err != nil {
return err
}
formatBackend , err := formatMetaGetFormatBackendFS ( rlk )
if err != nil {
rlk . Close ( )
return err
}
2022-05-30 13:58:37 -04:00
if formatBackend == formatBackendErasureSingle {
rlk . Close ( )
return errFreshDisk
}
2018-07-18 23:17:35 -04:00
if formatBackend != formatBackendFS {
rlk . Close ( )
return fmt . Errorf ( ` %s file: expected format-type: %s, found: %s ` , formatConfigFile , formatBackendFS , formatBackend )
}
format := & formatFS { }
err = jsonLoad ( rlk , format )
rlk . Close ( )
if err != nil {
return err
}
// Check if it needs to be updated
if format . ID != "" {
return nil
}
2019-01-09 13:13:04 -05:00
formatStartTime := time . Now ( ) . Round ( time . Second )
getElapsedTime := func ( ) string {
return time . Now ( ) . Round ( time . Second ) . Sub ( formatStartTime ) . String ( )
}
2020-11-02 20:52:13 -05:00
r := rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
2019-01-09 13:13:04 -05:00
2019-02-13 07:59:36 -05:00
var wlk * lock . LockedFile
2019-12-24 16:49:48 -05:00
var stop bool
for ! stop {
select {
2020-11-02 20:52:13 -05:00
case <- ctx . Done ( ) :
return fmt . Errorf ( "Initializing FS format stopped gracefully" )
default :
2019-12-24 16:49:48 -05:00
wlk , err = lock . TryLockedOpenFile ( fsFormatPath , os . O_RDWR , 0 )
if err == lock . ErrAlreadyLocked {
// Lock already present, sleep and attempt again
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 ( ) )
2020-11-02 20:52:13 -05:00
time . Sleep ( time . Duration ( r . Float64 ( ) * float64 ( 5 * time . Second ) ) )
2019-12-24 16:49:48 -05:00
continue
}
if err != nil {
return err
}
2019-02-13 07:59:36 -05:00
}
2020-11-02 20:52:13 -05:00
stop = true
2019-11-13 15:18:23 -05:00
}
defer wlk . Close ( )
2019-01-09 13:13:04 -05:00
2019-11-13 15:18:23 -05:00
if err = jsonLoad ( wlk , format ) ; err != nil {
return err
2018-07-18 23:17:35 -04:00
}
2019-11-13 15:18:23 -05:00
// Check if format needs to be updated
if format . ID != "" {
return nil
2019-02-13 07:59:36 -05:00
}
2019-11-13 15:18:23 -05:00
// Set new UUID to the format and save it
format . ID = mustGetUUID ( )
return jsonSave ( wlk , format )
2018-07-18 23:17:35 -04:00
}