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  }