mirror of
				https://github.com/minio/minio.git
				synced 2025-10-28 23:35:01 -04:00 
			
		
		
		
	
							parent
							
								
									99ddd35343
								
							
						
					
					
						commit
						76f4f20609
					
				| @ -20,8 +20,12 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/lock" | ||||
| ) | ||||
| 
 | ||||
| // fsFormat - structure holding 'fs' format. | ||||
| @ -47,6 +51,43 @@ type formatConfigV1 struct { | ||||
| 	XL     *xlFormat `json:"xl,omitempty"` // XL field holds xl format. | ||||
| } | ||||
| 
 | ||||
| func (f *formatConfigV1) WriteTo(lk *lock.LockedFile) (n int64, err error) { | ||||
| 	// Serialize to prepare to write to disk. | ||||
| 	var fbytes []byte | ||||
| 	fbytes, err = json.Marshal(f) | ||||
| 	if err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	if err = lk.Truncate(0); err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	_, err = lk.Write(fbytes) | ||||
| 	if err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	return int64(len(fbytes)), nil | ||||
| } | ||||
| 
 | ||||
| func (f *formatConfigV1) ReadFrom(lk *lock.LockedFile) (n int64, err error) { | ||||
| 	var fbytes []byte | ||||
| 	fi, err := lk.Stat() | ||||
| 	if err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	fbytes, err = ioutil.ReadAll(io.NewSectionReader(lk, 0, fi.Size())) | ||||
| 	if err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	if len(fbytes) == 0 { | ||||
| 		return 0, traceError(io.EOF) | ||||
| 	} | ||||
| 	// Decode `format.json`. | ||||
| 	if err = json.Unmarshal(fbytes, f); err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	} | ||||
| 	return int64(len(fbytes)), nil | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| 
 | ||||
| All disks online | ||||
|  | ||||
| @ -230,7 +230,7 @@ func fsOpenFile(readPath string, offset int64) (io.ReadCloser, int64, error) { | ||||
| 
 | ||||
| // Creates a file and copies data from incoming reader. Staging buffer is used by io.CopyBuffer. | ||||
| func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int64) (int64, error) { | ||||
| 	if filePath == "" || reader == nil || buf == nil { | ||||
| 	if filePath == "" || reader == nil { | ||||
| 		return 0, traceError(errInvalidArgument) | ||||
| 	} | ||||
| 
 | ||||
| @ -263,11 +263,18 @@ func fsCreateFile(filePath string, reader io.Reader, buf []byte, fallocSize int6 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	bytesWritten, err := io.CopyBuffer(writer, reader, buf) | ||||
| 	if err != nil { | ||||
| 		return 0, traceError(err) | ||||
| 	var bytesWritten int64 | ||||
| 	if buf != nil { | ||||
| 		bytesWritten, err = io.CopyBuffer(writer, reader, buf) | ||||
| 		if err != nil { | ||||
| 			return 0, traceError(err) | ||||
| 		} | ||||
| 	} else { | ||||
| 		bytesWritten, err = io.Copy(writer, reader) | ||||
| 		if err != nil { | ||||
| 			return 0, traceError(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return bytesWritten, nil | ||||
| } | ||||
| 
 | ||||
| @ -276,6 +283,12 @@ func fsRemoveUploadIDPath(basePath, uploadIDPath string) error { | ||||
| 	if basePath == "" || uploadIDPath == "" { | ||||
| 		return traceError(errInvalidArgument) | ||||
| 	} | ||||
| 	if err := checkPathLength(basePath); err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 	if err := checkPathLength(uploadIDPath); err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// List all the entries in uploadID. | ||||
| 	entries, err := readDir(uploadIDPath) | ||||
| @ -319,6 +332,26 @@ func fsFAllocate(fd int, offset int64, len int64) (err error) { | ||||
| // Renames source path to destination path, creates all the | ||||
| // missing parents if they don't exist. | ||||
| func fsRenameFile(sourcePath, destPath string) error { | ||||
| 	if err := checkPathLength(sourcePath); err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 	if err := checkPathLength(destPath); err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 	// Verify if source path exists. | ||||
| 	if _, err := os.Stat(preparePath(sourcePath)); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return traceError(errFileNotFound) | ||||
| 		} else if os.IsPermission(err) { | ||||
| 			return traceError(errFileAccessDenied) | ||||
| 		} else if isSysErrPathNotFound(err) { | ||||
| 			return traceError(errFileNotFound) | ||||
| 		} else if isSysErrNotDir(err) { | ||||
| 			// File path cannot be verified since one of the parents is a file. | ||||
| 			return traceError(errFileAccessDenied) | ||||
| 		} | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 	if err := mkdirAll(pathutil.Dir(destPath), 0777); err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
|  | ||||
| @ -26,6 +26,31 @@ import ( | ||||
| 	"github.com/minio/minio/pkg/lock" | ||||
| ) | ||||
| 
 | ||||
| func TestFSRenameFile(t *testing.T) { | ||||
| 	// create posix test setup | ||||
| 	_, path, err := newPosixTestSetup() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create posix test setup, %s", err) | ||||
| 	} | ||||
| 	defer removeAll(path) | ||||
| 
 | ||||
| 	if err = fsMkdir(pathJoin(path, "testvolume1")); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNotFound { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if err = fsRenameFile(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), pathJoin(path, "testvolume2")); errorCause(err) != errFileNameTooLong { | ||||
| 		t.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| 	if err = fsRenameFile(pathJoin(path, "testvolume1"), pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errorCause(err) != errFileNameTooLong { | ||||
| 		t.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFSStats(t *testing.T) { | ||||
| 	// create posix test setup | ||||
| 	_, path, err := newPosixTestSetup() | ||||
| @ -48,9 +73,8 @@ func TestFSStats(t *testing.T) { | ||||
| 		t.Fatalf("Unable to create volume, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var buf = make([]byte, 4096) | ||||
| 	var reader = bytes.NewReader([]byte("Hello, world")) | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 	// Seek back. | ||||
| @ -60,7 +84,7 @@ func TestFSStats(t *testing.T) { | ||||
| 		t.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "path/to/success-file"), reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "path/to/success-file"), reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 	// Seek back. | ||||
| @ -174,9 +198,8 @@ func TestFSCreateAndOpen(t *testing.T) { | ||||
| 		t.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var buf = make([]byte, 4096) | ||||
| 	var reader = bytes.NewReader([]byte("Hello, world")) | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 	// Seek back. | ||||
| @ -204,7 +227,7 @@ func TestFSCreateAndOpen(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	for i, testCase := range testCases { | ||||
| 		_, err = fsCreateFile(pathJoin(path, testCase.srcVol, testCase.srcPath), reader, buf, reader.Size()) | ||||
| 		_, err = fsCreateFile(pathJoin(path, testCase.srcVol, testCase.srcPath), reader, nil, 0) | ||||
| 		if errorCause(err) != testCase.expectedErr { | ||||
| 			t.Errorf("Test case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err) | ||||
| 		} | ||||
| @ -297,15 +320,14 @@ func TestFSRemoves(t *testing.T) { | ||||
| 		t.Fatalf("Unable to create directory, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var buf = make([]byte, 4096) | ||||
| 	var reader = bytes.NewReader([]byte("Hello, world")) | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file"), reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 	// Seek back. | ||||
| 	reader.Seek(0, 0) | ||||
| 
 | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file-new"), reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(pathJoin(path, "success-vol", "success-file-new"), reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 	// Seek back. | ||||
| @ -417,9 +439,8 @@ func TestFSRemoveMeta(t *testing.T) { | ||||
| 
 | ||||
| 	filePath := pathJoin(fsPath, "success-vol", "success-file") | ||||
| 
 | ||||
| 	var buf = make([]byte, 4096) | ||||
| 	var reader = bytes.NewReader([]byte("Hello, world")) | ||||
| 	if _, err = fsCreateFile(filePath, reader, buf, reader.Size()); err != nil { | ||||
| 	if _, err = fsCreateFile(filePath, reader, nil, 0); err != nil { | ||||
| 		t.Fatalf("Unable to create file, %s", err) | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -18,6 +18,8 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| @ -25,6 +27,7 @@ import ( | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| 	"github.com/minio/minio/pkg/lock" | ||||
| 	"github.com/minio/minio/pkg/mimedb" | ||||
| 	"github.com/tidwall/gjson" | ||||
| @ -225,9 +228,20 @@ const ( | ||||
| 	// FS backend meta format. | ||||
| 	fsMetaFormat = "fs" | ||||
| 
 | ||||
| 	// FS backend format version. | ||||
| 	fsFormatVersion = fsFormatV2 | ||||
| 
 | ||||
| 	// Add more constants here. | ||||
| ) | ||||
| 
 | ||||
| // FS format version strings. | ||||
| const ( | ||||
| 	fsFormatV1 = "1" // Previous format. | ||||
| 	fsFormatV2 = "2" // Current format. | ||||
| 	// Proceed to add "3" when we | ||||
| 	// change the backend format in future. | ||||
| ) | ||||
| 
 | ||||
| // newFSMetaV1 - initializes new fsMetaV1. | ||||
| func newFSMetaV1() (fsMeta fsMetaV1) { | ||||
| 	fsMeta = fsMetaV1{} | ||||
| @ -237,58 +251,167 @@ func newFSMetaV1() (fsMeta fsMetaV1) { | ||||
| 	return fsMeta | ||||
| } | ||||
| 
 | ||||
| // newFSFormatV1 - initializes new formatConfigV1 with FS format info. | ||||
| func newFSFormatV1() (format *formatConfigV1) { | ||||
| // newFSFormatV2 - initializes new formatConfigV1 with FS format version 2. | ||||
| func newFSFormatV2() (format *formatConfigV1) { | ||||
| 	return &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 			Version: fsFormatV2, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // loads format.json from minioMetaBucket if it exists. | ||||
| func loadFormatFS(fsPath string) (*formatConfigV1, error) { | ||||
| 	rlk, err := lock.RLockedOpenFile(pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile)) | ||||
| 	if err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil, errUnformattedDisk | ||||
| // Checks if input format is version 1 and 2. | ||||
| func isFSValidFormat(formatCfg *formatConfigV1) bool { | ||||
| 	// Supported format versions. | ||||
| 	var supportedFormatVersions = []string{ | ||||
| 		fsFormatV1, | ||||
| 		fsFormatV2, | ||||
| 		// New supported versions here. | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for supported format versions. | ||||
| 	for _, version := range supportedFormatVersions { | ||||
| 		if formatCfg.FS.Version == version { | ||||
| 			return true | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer rlk.Close() | ||||
| 
 | ||||
| 	formatBytes, err := ioutil.ReadAll(rlk) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	format := &formatConfigV1{} | ||||
| 	if err = json.Unmarshal(formatBytes, format); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return format, nil | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // writes FS format (format.json) into minioMetaBucket. | ||||
| func saveFormatFS(formatPath string, fsFormat *formatConfigV1) error { | ||||
| 	metadataBytes, err := json.Marshal(fsFormat) | ||||
| 	if err != nil { | ||||
| // errFSFormatOld- old fs format. | ||||
| var errFSFormatOld = errors.New("old FS format found") | ||||
| 
 | ||||
| // Checks if the loaded `format.json` is valid and | ||||
| // is expected to be of the requested version. | ||||
| func checkFormatFS(format *formatConfigV1, formatVersion string) error { | ||||
| 	if format == nil { | ||||
| 		return errUnexpected | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate if we have the same format. | ||||
| 	if format.Format != "fs" { | ||||
| 		return fmt.Errorf("Unable to recognize backend format, Disk is not in FS format. %s", format.Format) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if format is currently supported. | ||||
| 	if !isFSValidFormat(format) { | ||||
| 		return errCorruptedFormat | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for format version is current. | ||||
| 	if format.FS.Version != formatVersion { | ||||
| 		return errFSFormatOld | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // This is just kept as reference, there is no sanity | ||||
| // check for FS format in version "1". | ||||
| func checkFormatSanityFSV1(fsPath string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Check for sanity of FS format in version "2". | ||||
| func checkFormatSanityFSV2(fsPath string) error { | ||||
| 	buckets, err := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix)) | ||||
| 	if err != nil && err != errFileNotFound { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Attempt to validate all the buckets have a sanitized backend. | ||||
| 	for _, bucket := range buckets { | ||||
| 		entries, rerr := readDir(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix, bucket)) | ||||
| 		if rerr != nil { | ||||
| 			return rerr | ||||
| 		} | ||||
| 
 | ||||
| 		var expectedConfigs = append(bucketMetadataConfigs, objectMetaPrefix+"/") | ||||
| 		entriesSet := set.CreateStringSet(entries...) | ||||
| 		expectedConfigsSet := set.CreateStringSet(expectedConfigs...) | ||||
| 
 | ||||
| 		// Entries found shouldn't be more than total | ||||
| 		// expected config directories, files. | ||||
| 		if len(entriesSet) > len(expectedConfigsSet) { | ||||
| 			return errCorruptedFormat | ||||
| 		} | ||||
| 
 | ||||
| 		// Look for the difference between entries and the | ||||
| 		// expected config set, resulting entries if they | ||||
| 		// intersect with original entries set we know | ||||
| 		// that the backend has unexpected files. | ||||
| 		if !entriesSet.Difference(expectedConfigsSet).IsEmpty() { | ||||
| 			return errCorruptedFormat | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Check for sanity of FS format for a given version. | ||||
| func checkFormatSanityFS(fsPath string, fsFormatVersion string) (err error) { | ||||
| 	switch fsFormatVersion { | ||||
| 	case fsFormatV2: | ||||
| 		err = checkFormatSanityFSV2(fsPath) | ||||
| 	default: | ||||
| 		err = errCorruptedFormat | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Initializes a new `format.json` if not present, validates `format.json` | ||||
| // if already present and migrates to newer version if necessary. Returns | ||||
| // the final format version. | ||||
| func initFormatFS(fsPath, fsUUID string) (err error) { | ||||
| 	fsFormatPath := pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile) | ||||
| 
 | ||||
| 	// fsFormatJSONFile - format.json file stored in minioMetaBucket(.minio.sys) directory. | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(formatPath), os.O_CREATE|os.O_WRONLY, 0600) | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return traceError(err) | ||||
| 	} | ||||
| 	defer lk.Close() | ||||
| 
 | ||||
| 	_, err = lk.Write(metadataBytes) | ||||
| 	// Success. | ||||
| 	return err | ||||
| 	var format = &formatConfigV1{} | ||||
| 	_, err = format.ReadFrom(lk) | ||||
| 	// For all unexpected errors, we return. | ||||
| 	if err != nil && errorCause(err) != io.EOF { | ||||
| 		return traceError(fmt.Errorf("Unable to load 'format.json', %s", err)) | ||||
| 	} | ||||
| 
 | ||||
| 	// If we couldn't read anything, The disk is unformatted. | ||||
| 	if errorCause(err) == io.EOF { | ||||
| 		err = errUnformattedDisk | ||||
| 		format = newFSFormatV2() | ||||
| 	} else { | ||||
| 		// Validate loaded `format.json`. | ||||
| 		err = checkFormatFS(format, fsFormatVersion) | ||||
| 		if err != nil && err != errFSFormatOld { | ||||
| 			return traceError(fmt.Errorf("Unable to validate 'format.json', %s", err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Disk is in old format migrate object metadata. | ||||
| 	if err == errFSFormatOld { | ||||
| 		if merr := migrateFSObject(fsPath, fsUUID); merr != nil { | ||||
| 			return merr | ||||
| 		} | ||||
| 
 | ||||
| 		// Initialize format v2. | ||||
| 		format = newFSFormatV2() | ||||
| 	} | ||||
| 
 | ||||
| 	// Rewrite or write format.json depending on if disk | ||||
| 	// unformatted and if format is old. | ||||
| 	if err == errUnformattedDisk || err == errFSFormatOld { | ||||
| 		if _, err = format.WriteTo(lk); err != nil { | ||||
| 			return traceError(fmt.Errorf("Unable to initialize 'format.json', %s", err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for sanity. | ||||
| 	return checkFormatSanityFS(fsPath, format.FS.Version) | ||||
| } | ||||
| 
 | ||||
| // Return if the part info in uploadedParts and completeParts are same. | ||||
|  | ||||
| @ -58,7 +58,7 @@ func TestReadFSMetadata(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct the full path of fs.json | ||||
| 	fsPath := pathJoin("buckets", bucketName, objectName, "fs.json") | ||||
| 	fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") | ||||
| 	fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) | ||||
| 
 | ||||
| 	rlk, err := fs.rwPool.Open(fsPath) | ||||
| @ -95,7 +95,7 @@ func TestWriteFSMetadata(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct the full path of fs.json | ||||
| 	fsPath := pathJoin("buckets", bucketName, objectName, "fs.json") | ||||
| 	fsPath := pathJoin(bucketMetaPrefix, bucketName, objectMetaPrefix, objectName, "fs.json") | ||||
| 	fsPath = pathJoin(fs.fsPath, minioMetaBucket, fsPath) | ||||
| 
 | ||||
| 	rlk, err := fs.rwPool.Open(fsPath) | ||||
|  | ||||
| @ -754,7 +754,7 @@ func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, upload | ||||
| 
 | ||||
| 	// Wait for any competing PutObject() operation on bucket/object, since same namespace | ||||
| 	// would be acquired for `fs.json`. | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 	metaFile, err := fs.rwPool.Create(fsMetaPath) | ||||
| 	if err != nil { | ||||
| 		fs.rwPool.Close(fsMetaPathMultipart) | ||||
|  | ||||
							
								
								
									
										155
									
								
								cmd/fs-v1.go
									
									
									
									
									
								
							
							
						
						
									
										155
									
								
								cmd/fs-v1.go
									
									
									
									
									
								
							| @ -24,6 +24,7 @@ import ( | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"syscall" | ||||
| @ -72,15 +73,117 @@ func initMetaVolumeFS(fsPath, fsUUID string) error { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Migrate FS object is a place holder code for all | ||||
| // FS format migrations. | ||||
| func migrateFSObject(fsPath, fsUUID string) (err error) { | ||||
| 	// Writing message here is important for servers being upgraded. | ||||
| 	log.Println("Please do not stop the server.") | ||||
| 
 | ||||
| 	ch := make(chan os.Signal) | ||||
| 	defer signal.Stop(ch) | ||||
| 	defer close(ch) | ||||
| 
 | ||||
| 	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			_, ok := <-ch | ||||
| 			if !ok { | ||||
| 				break | ||||
| 			} | ||||
| 			log.Println("Please wait server is being upgraded..") | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	return migrateFSFormatV1ToV2(fsPath, fsUUID) | ||||
| } | ||||
| 
 | ||||
| // List all buckets at meta bucket prefix in `.minio.sys/buckets/` path. | ||||
| // This is implemented to avoid a bug on windows with using readDir(). | ||||
| func fsReaddirMetaBuckets(fsPath string) ([]string, error) { | ||||
| 	f, err := os.Open(preparePath(pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix))) | ||||
| 	if err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil, errFileNotFound | ||||
| 		} else if os.IsPermission(err) { | ||||
| 			return nil, errFileAccessDenied | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return f.Readdirnames(-1) | ||||
| } | ||||
| 
 | ||||
| var bucketMetadataConfigs = []string{ | ||||
| 	bucketNotificationConfig, | ||||
| 	bucketListenerConfig, | ||||
| 	bucketPolicyConfig, | ||||
| } | ||||
| 
 | ||||
| // Attempts to migrate old object metadata files to newer format | ||||
| // | ||||
| // i.e | ||||
| //    ------------------------------------------------------- | ||||
| //    .minio.sys/buckets/<bucket_name>/<object_path>/fs.json - V1 | ||||
| //    ------------------------------------------------------- | ||||
| //    .minio.sys/buckets/<bucket_name>/objects/<object_path>/fs.json - V2 | ||||
| //    ------------------------------------------------------- | ||||
| // | ||||
| func migrateFSFormatV1ToV2(fsPath, fsUUID string) (err error) { | ||||
| 	metaBucket := pathJoin(fsPath, minioMetaBucket, bucketConfigPrefix) | ||||
| 
 | ||||
| 	var buckets []string | ||||
| 	buckets, err = fsReaddirMetaBuckets(fsPath) | ||||
| 	if err != nil && err != errFileNotFound { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Migrate all buckets present. | ||||
| 	for _, bucket := range buckets { | ||||
| 		// Temporary bucket of form .UUID-bucket. | ||||
| 		tmpBucket := fmt.Sprintf(".%s-%s", fsUUID, bucket) | ||||
| 
 | ||||
| 		// Rename existing bucket as `.UUID-bucket`. | ||||
| 		if err = fsRenameFile(pathJoin(metaBucket, bucket), pathJoin(metaBucket, tmpBucket)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// Create a new bucket name with name as `bucket`. | ||||
| 		if err = fsMkdir(pathJoin(metaBucket, bucket)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		///  Rename all bucket metadata files to newly created `bucket`. | ||||
| 		for _, bucketMetaFile := range bucketMetadataConfigs { | ||||
| 			if err = fsRenameFile(pathJoin(metaBucket, tmpBucket, bucketMetaFile), | ||||
| 				pathJoin(metaBucket, bucket, bucketMetaFile)); err != nil { | ||||
| 				if errorCause(err) != errFileNotFound { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// Finally rename the temporary bucket to `bucket/objects` directory. | ||||
| 		if err = fsRenameFile(pathJoin(metaBucket, tmpBucket), | ||||
| 			pathJoin(metaBucket, bucket, objectMetaPrefix)); err != nil { | ||||
| 			if errorCause(err) != errFileNotFound { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("Migrating bucket metadata format from \"%s\" to newer format \"%s\"... completed successfully.", fsFormatV1, fsFormatV2) | ||||
| 
 | ||||
| 	// If all goes well we return success. | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // newFSObjectLayer - initialize new fs object layer. | ||||
| func newFSObjectLayer(fsPath string) (ObjectLayer, error) { | ||||
| 	if fsPath == "" { | ||||
| 		return nil, errInvalidArgument | ||||
| 	} | ||||
| 
 | ||||
| 	var err error | ||||
| 	// Disallow relative paths, figure out absolute paths. | ||||
| 	fsPath, err = filepath.Abs(fsPath) | ||||
| 	fsPath, err := filepath.Abs(fsPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -108,26 +211,6 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { | ||||
| 		return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Load `format.json`. | ||||
| 	format, err := loadFormatFS(fsPath) | ||||
| 	if err != nil && err != errUnformattedDisk { | ||||
| 		return nil, fmt.Errorf("Unable to load 'format.json', %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// If the `format.json` doesn't exist create one. | ||||
| 	if err == errUnformattedDisk { | ||||
| 		fsFormatPath := pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile) | ||||
| 		// Initialize format.json, if already exists overwrite it. | ||||
| 		if serr := saveFormatFS(fsFormatPath, newFSFormatV1()); serr != nil { | ||||
| 			return nil, fmt.Errorf("Unable to initialize 'format.json', %s", serr) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate if we have the same format. | ||||
| 	if err == nil && format.Format != "fs" { | ||||
| 		return nil, fmt.Errorf("Unable to recognize backend format, Disk is not in FS format. %s", format.Format) | ||||
| 	} | ||||
| 
 | ||||
| 	// Initialize fs objects. | ||||
| 	fs := &fsObjects{ | ||||
| 		fsPath: fsPath, | ||||
| @ -141,6 +224,17 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Initialize `format.json`. | ||||
| 	if err = initFormatFS(fsPath, fsUUID); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Once initialized hold read lock for the entire operation | ||||
| 	// of filesystem backend. | ||||
| 	if _, err = fs.rwPool.Open(pathJoin(fsPath, minioMetaBucket, fsFormatJSONFile)); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Initialize and load bucket policies. | ||||
| 	err = initBucketPolicies(fs) | ||||
| 	if err != nil { | ||||
| @ -159,6 +253,9 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) { | ||||
| 
 | ||||
| // Should be called when process shuts down. | ||||
| func (fs fsObjects) Shutdown() error { | ||||
| 	// Close the format.json read lock. | ||||
| 	fs.rwPool.Close(pathJoin(fs.fsPath, minioMetaBucket, fsFormatJSONFile)) | ||||
| 
 | ||||
| 	// Cleanup and delete tmp uuid. | ||||
| 	return fsRemoveAll(pathJoin(fs.fsPath, minioMetaTmpBucket, fs.fsUUID)) | ||||
| } | ||||
| @ -238,7 +335,7 @@ func (fs fsObjects) ListBuckets() ([]BucketInfo, error) { | ||||
| 		return nil, traceError(err) | ||||
| 	} | ||||
| 	var bucketInfos []BucketInfo | ||||
| 	entries, err := readDir(preparePath(fs.fsPath)) | ||||
| 	entries, err := readDir(fs.fsPath) | ||||
| 	if err != nil { | ||||
| 		return nil, toObjectErr(traceError(errDiskNotFound)) | ||||
| 	} | ||||
| @ -322,7 +419,7 @@ func (fs fsObjects) CopyObject(srcBucket, srcObject, dstBucket, dstObject string | ||||
| 	// Check if this request is only metadata update. | ||||
| 	cpMetadataOnly := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) | ||||
| 	if cpMetadataOnly { | ||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fsMetaJSONFile) | ||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, objectMetaPrefix, srcObject, fsMetaJSONFile) | ||||
| 		var wlk *lock.LockedFile | ||||
| 		wlk, err = fs.rwPool.Write(fsMetaPath) | ||||
| 		if err != nil { | ||||
| @ -395,7 +492,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, | ||||
| 	} | ||||
| 
 | ||||
| 	if bucket != minioMetaBucket { | ||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 		_, err = fs.rwPool.Open(fsMetaPath) | ||||
| 		if err != nil && err != errFileNotFound { | ||||
| 			return toObjectErr(traceError(err), bucket, object) | ||||
| @ -437,7 +534,7 @@ func (fs fsObjects) GetObject(bucket, object string, offset int64, length int64, | ||||
| // getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo. | ||||
| func (fs fsObjects) getObjectInfo(bucket, object string) (ObjectInfo, error) { | ||||
| 	fsMeta := fsMetaV1{} | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 
 | ||||
| 	// Read `fs.json` to perhaps contend with | ||||
| 	// parallel Put() operations. | ||||
| @ -520,7 +617,7 @@ func (fs fsObjects) PutObject(bucket string, object string, size int64, data io. | ||||
| 	var wlk *lock.LockedFile | ||||
| 	if bucket != minioMetaBucket { | ||||
| 		bucketMetaDir := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix) | ||||
| 		fsMetaPath := pathJoin(bucketMetaDir, bucket, object, fsMetaJSONFile) | ||||
| 		fsMetaPath := pathJoin(bucketMetaDir, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 		wlk, err = fs.rwPool.Create(fsMetaPath) | ||||
| 		if err != nil { | ||||
| 			return ObjectInfo{}, toObjectErr(traceError(err), bucket, object) | ||||
| @ -647,7 +744,7 @@ func (fs fsObjects) DeleteObject(bucket, object string) error { | ||||
| 	} | ||||
| 
 | ||||
| 	minioMetaBucketDir := pathJoin(fs.fsPath, minioMetaBucket) | ||||
| 	fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(minioMetaBucketDir, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 	if bucket != minioMetaBucket { | ||||
| 		rwlk, lerr := fs.rwPool.Write(fsMetaPath) | ||||
| 		if lerr == nil { | ||||
| @ -701,7 +798,7 @@ func (fs fsObjects) listDirFactory(isLeaf isLeafFunc) listDirFunc { | ||||
| // getObjectETag is a helper function, which returns only the md5sum | ||||
| // of the file on the disk. | ||||
| func (fs fsObjects) getObjectETag(bucket, entry string) (string, error) { | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, entry, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, entry, fsMetaJSONFile) | ||||
| 
 | ||||
| 	// Read `fs.json` to perhaps contend with | ||||
| 	// parallel Put() operations. | ||||
|  | ||||
| @ -22,6 +22,8 @@ import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/lock" | ||||
| ) | ||||
| 
 | ||||
| // TestNewFS - tests initialization of all input disks | ||||
| @ -85,8 +87,8 @@ func TestFSShutdown(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TestFSLoadFormatFS - test loadFormatFS with healty and faulty disks | ||||
| func TestFSLoadFormatFS(t *testing.T) { | ||||
| // Tests migrating FS format without .minio.sys/buckets. | ||||
| func TestFSMigrateObjectWithoutObjects(t *testing.T) { | ||||
| 	// Prepare for testing | ||||
| 	disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) | ||||
| 	defer removeAll(disk) | ||||
| @ -100,13 +102,364 @@ func TestFSLoadFormatFS(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	if err := saveFormatFS(preparePath(fsFormatPath), newFSFormatV1()); err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	formatCfg := &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := loadFormatFS(disk) | ||||
| 
 | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = initFormatFS(disk, uuid); err != nil { | ||||
| 		t.Fatal("Should not fail with unexpected", err) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{} | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.ReadFrom(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 	if formatCfg.FS.Version != fsFormatV2 { | ||||
| 		t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests migrating FS format without .minio.sys/buckets. | ||||
| func TestFSMigrateObjectWithErr(t *testing.T) { | ||||
| 	// Prepare for testing | ||||
| 	disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) | ||||
| 	defer removeAll(disk) | ||||
| 
 | ||||
| 	// Assign a new UUID. | ||||
| 	uuid := mustGetUUID() | ||||
| 
 | ||||
| 	// Initialize meta volume, if volume already exists ignores it. | ||||
| 	if err := initMetaVolumeFS(disk, uuid); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	formatCfg := &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "10", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = initFormatFS(disk, uuid); err != nil { | ||||
| 		if errorCause(err).Error() != | ||||
| 			"Unable to validate 'format.json', corrupted backend format" { | ||||
| 			t.Fatal("Should not fail with unexpected", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath = pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	formatCfg = &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "garbage", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = initFormatFS(disk, uuid); err != nil { | ||||
| 		if errorCause(err).Error() != | ||||
| 			"Unable to validate 'format.json', Unable to recognize backend format, Disk is not in FS format. garbage" { | ||||
| 			t.Fatal("Should not fail with unexpected", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Tests migrating FS format with .minio.sys/buckets filled with | ||||
| // object metadata. | ||||
| func TestFSMigrateObjectWithObjects(t *testing.T) { | ||||
| 	// Prepare for testing | ||||
| 	disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) | ||||
| 	defer removeAll(disk) | ||||
| 
 | ||||
| 	// Assign a new UUID. | ||||
| 	uuid := mustGetUUID() | ||||
| 
 | ||||
| 	// Initialize meta volume, if volume already exists ignores it. | ||||
| 	if err := initMetaVolumeFS(disk, uuid); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	formatCfg := &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 		}, | ||||
| 	} | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct the full path of fs.json | ||||
| 	fsPath1 := pathJoin(bucketMetaPrefix, "testvolume1", "my-object1", fsMetaJSONFile) | ||||
| 	fsPath1 = pathJoin(disk, minioMetaBucket, fsPath1) | ||||
| 
 | ||||
| 	fsMetaJSON := `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900e3f461b4f"}` | ||||
| 	if _, err = fsCreateFile(fsPath1, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct the full path of fs.json | ||||
| 	fsPath2 := pathJoin(bucketMetaPrefix, "testvolume2", "my-object2", fsMetaJSONFile) | ||||
| 	fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) | ||||
| 
 | ||||
| 	fsMetaJSON = `{"version":"1.0.0","format":"fs","minio":{"release":"DEVELOPMENT.2017-03-27T02-26-33Z"},"meta":{"md5Sum":"467886be95c8ecfd71a2900eff461b4d"}` | ||||
| 	if _, err = fsCreateFile(fsPath2, bytes.NewReader([]byte(fsMetaJSON)), nil, 0); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct the full path of policy.json | ||||
| 	ppath := pathJoin(bucketMetaPrefix, "testvolume2", bucketPolicyConfig) | ||||
| 	ppath = pathJoin(disk, minioMetaBucket, ppath) | ||||
| 
 | ||||
| 	policyJSON := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testbucket/*"],"Sid":""}]}` | ||||
| 	if _, err = fsCreateFile(ppath, bytes.NewReader([]byte(policyJSON)), nil, 0); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = initFormatFS(disk, mustGetUUID()); err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fsPath2 = pathJoin(bucketMetaPrefix, "testvolume2", objectMetaPrefix, "my-object2", fsMetaJSONFile) | ||||
| 	fsPath2 = pathJoin(disk, minioMetaBucket, fsPath2) | ||||
| 	fi, err := fsStatFile(fsPath2) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Path should exist and accessible after migration", err) | ||||
| 	} | ||||
| 	if fi.IsDir() { | ||||
| 		t.Fatalf("Unexpected path %s should be a file", fsPath2) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{} | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.ReadFrom(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| 	if formatCfg.FS.Version != fsFormatV2 { | ||||
| 		t.Fatalf("Unexpected version detected expected \"%s\", got %s", fsFormatV2, formatCfg.FS.Version) | ||||
| 	} | ||||
| 
 | ||||
| 	ppath = pathJoin(bucketMetaPrefix, "testvolume2", "acl.json") | ||||
| 	ppath = pathJoin(disk, minioMetaBucket, ppath) | ||||
| 
 | ||||
| 	if _, err = fsCreateFile(ppath, bytes.NewReader([]byte("")), nil, 0); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = initFormatFS(disk, mustGetUUID()); errorCause(err) != errCorruptedFormat { | ||||
| 		t.Fatal("Should not fail here", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TestFSCheckFormatFSErr - test loadFormatFS loading older format. | ||||
| func TestFSCheckFormatFSErr(t *testing.T) { | ||||
| 	// Prepare for testing | ||||
| 	disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) | ||||
| 	defer removeAll(disk) | ||||
| 
 | ||||
| 	// Assign a new UUID. | ||||
| 	uuid := mustGetUUID() | ||||
| 
 | ||||
| 	// Initialize meta volume, if volume already exists ignores it. | ||||
| 	if err := initMetaVolumeFS(disk, uuid); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	formatCfg := &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{} | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = formatCfg.ReadFrom(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errFSFormatOld { | ||||
| 		t.Fatal("Should not fail with unexpected", err) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "10", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = checkFormatFS(formatCfg, fsFormatVersion); errorCause(err) != errCorruptedFormat { | ||||
| 		t.Fatal("Should not fail with unexpected", err) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "garbage", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "1", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { | ||||
| 		if errorCause(err).Error() != "Unable to recognize backend format, Disk is not in FS format. garbage" { | ||||
| 			t.Fatal("Should not fail with unexpected", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err = checkFormatFS(nil, fsFormatVersion); errorCause(err) != errUnexpected { | ||||
| 		t.Fatal("Should fail with errUnexpected, but found", err) | ||||
| 	} | ||||
| 
 | ||||
| 	formatCfg = &formatConfigV1{ | ||||
| 		Version: "1", | ||||
| 		Format:  "fs", | ||||
| 		FS: &fsFormat{ | ||||
| 			Version: "2", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = formatCfg.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Should not fail. | ||||
| 	if err = checkFormatFS(formatCfg, fsFormatVersion); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TestFSCheckFormatFS - test loadFormatFS with healty and faulty disks | ||||
| func TestFSCheckFormatFS(t *testing.T) { | ||||
| 	// Prepare for testing | ||||
| 	disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix()) | ||||
| 	defer removeAll(disk) | ||||
| 
 | ||||
| 	// Assign a new UUID. | ||||
| 	uuid := mustGetUUID() | ||||
| 
 | ||||
| 	// Initialize meta volume, if volume already exists ignores it. | ||||
| 	if err := initMetaVolumeFS(disk, uuid); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fsFormatPath := pathJoin(disk, minioMetaBucket, fsFormatJSONFile) | ||||
| 	lk, err := lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	format := newFSFormatV2() | ||||
| 	_, err = format.WriteTo(lk) | ||||
| 	lk.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Loading corrupted format file | ||||
| 	file, err := os.OpenFile(preparePath(fsFormatPath), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) | ||||
| 	if err != nil { | ||||
| @ -114,15 +467,24 @@ func TestFSLoadFormatFS(t *testing.T) { | ||||
| 	} | ||||
| 	file.Write([]byte{'b'}) | ||||
| 	file.Close() | ||||
| 	_, err = loadFormatFS(disk) | ||||
| 
 | ||||
| 	lk, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDWR|os.O_CREATE, 0600) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	format = &formatConfigV1{} | ||||
| 	_, err = format.ReadFrom(lk) | ||||
| 	lk.Close() | ||||
| 	if err == nil { | ||||
| 		t.Fatal("Should return an error here") | ||||
| 	} | ||||
| 
 | ||||
| 	// Loading format file from disk not found. | ||||
| 	removeAll(disk) | ||||
| 	_, err = loadFormatFS(disk) | ||||
| 	if err != nil && err != errUnformattedDisk { | ||||
| 		t.Fatal("Should return unformatted disk, but got", err) | ||||
| 	_, err = lock.LockedOpenFile(preparePath(fsFormatPath), os.O_RDONLY, 0600) | ||||
| 	if err != nil && !os.IsNotExist(err) { | ||||
| 		t.Fatal("Should return 'format.json' does not exist, but got", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -32,6 +32,9 @@ const ( | ||||
| 	// Buckets meta prefix. | ||||
| 	bucketMetaPrefix = "buckets" | ||||
| 
 | ||||
| 	// Objects meta prefix. | ||||
| 	objectMetaPrefix = "objects" | ||||
| 
 | ||||
| 	// Md5Sum of empty string. | ||||
| 	emptyStrMd5Sum = "d41d8cd98f00b204e9800998ecf8427e" | ||||
| ) | ||||
|  | ||||
| @ -32,6 +32,8 @@ func readDir(dirPath string) (entries []string, err error) { | ||||
| 		// File is really not found. | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil, errFileNotFound | ||||
| 		} else if os.IsPermission(err) { | ||||
| 			return nil, errFileAccessDenied | ||||
| 		} | ||||
| 
 | ||||
| 		// File path cannot be verified since one of the parents is a file. | ||||
|  | ||||
| @ -291,7 +291,7 @@ func (s *posix) ListVols() (volsInfo []VolInfo, err error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	volsInfo, err = listVols(preparePath(s.diskPath)) | ||||
| 	volsInfo, err = listVols(s.diskPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @ -82,7 +82,7 @@ func (m *ServerMux) handleServiceSignals() error { | ||||
| 
 | ||||
| 	// Wait for SIGTERM in a go-routine. | ||||
| 	trapCh := signalTrap(os.Interrupt, syscall.SIGTERM) | ||||
| 	go func(<-chan bool) { | ||||
| 	go func(trapCh <-chan bool) { | ||||
| 		<-trapCh | ||||
| 		globalServiceSignalCh <- serviceStop | ||||
| 	}(trapCh) | ||||
|  | ||||
| @ -81,7 +81,7 @@ An example here shows how the contention is handled with GetObject(). | ||||
| GetObject() holds a read lock on `fs.json`. | ||||
| 
 | ||||
| ```go | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 	rlk, err := fs.rwPool.Open(fsMetaPath) | ||||
| 	if err != nil { | ||||
| 		return toObjectErr(traceError(err), bucket, object) | ||||
| @ -98,7 +98,7 @@ GetObject() holds a read lock on `fs.json`. | ||||
| A concurrent PutObject is requested on the same object, PutObject() attempts a write lock on `fs.json`. | ||||
| 
 | ||||
| ```go | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fsMetaJSONFile) | ||||
| 	fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, objectMetaPrefix, object, fsMetaJSONFile) | ||||
| 	wlk, err := fs.rwPool.Create(fsMetaPath) | ||||
| 	if err != nil { | ||||
| 		return ObjectInfo{}, toObjectErr(err, bucket, object) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user