mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
	
	
		
			329 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
		
		
			
		
	
	
			329 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
|  | /* | ||
|  |  * Minio Cloud Storage, (C) 2018 Minio, Inc. | ||
|  |  * | ||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||
|  |  * you may not use this file except in compliance with the License. | ||
|  |  * You may obtain a copy of the License at | ||
|  |  * | ||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||
|  |  * | ||
|  |  * Unless required by applicable law or agreed to in writing, software | ||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
|  |  * See the License for the specific language governing permissions and | ||
|  |  * limitations under the License. | ||
|  |  */ | ||
|  | 
 | ||
|  | package cmd | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"errors" | ||
|  | 	"fmt" | ||
|  | 	"io" | ||
|  | 	"os" | ||
|  | 	"path/filepath" | ||
|  | 	"reflect" | ||
|  | 	"syscall" | ||
|  | 
 | ||
|  | 	errors2 "github.com/minio/minio/pkg/errors" | ||
|  | ) | ||
|  | 
 | ||
|  | const ( | ||
|  | 	// Represents Cache format json holding details on all other cache drives in use.
 | ||
|  | 	formatCache = "cache" | ||
|  | 
 | ||
|  | 	// formatCacheV1.Cache.Version
 | ||
|  | 	formatCacheVersionV1 = "1" | ||
|  | 
 | ||
|  | 	formatMetaVersion1 = "1" | ||
|  | ) | ||
|  | 
 | ||
|  | // Represents the current cache structure with list of
 | ||
|  | // disks comprising the disk cache
 | ||
|  | // formatCacheV1 - structure holds format config version '1'.
 | ||
|  | type formatCacheV1 struct { | ||
|  | 	formatMetaV1 | ||
|  | 	Cache struct { | ||
|  | 		Version string `json:"version"` // Version of 'cache' format.
 | ||
|  | 		This    string `json:"this"`    // This field carries assigned disk uuid.
 | ||
|  | 		// Disks field carries the input disk order generated the first
 | ||
|  | 		// time when fresh disks were supplied.
 | ||
|  | 		Disks []string `json:"disks"` | ||
|  | 	} `json:"cache"` // Cache field holds cache format.
 | ||
|  | } | ||
|  | 
 | ||
|  | // Used to detect the version of "cache" format.
 | ||
|  | type formatCacheVersionDetect struct { | ||
|  | 	Cache struct { | ||
|  | 		Version string `json:"version"` | ||
|  | 	} `json:"cache"` | ||
|  | } | ||
|  | 
 | ||
|  | // Return a slice of format, to be used to format uninitialized disks.
 | ||
|  | func newFormatCacheV1(drives []string) []*formatCacheV1 { | ||
|  | 	diskCount := len(drives) | ||
|  | 	var disks = make([]string, diskCount) | ||
|  | 
 | ||
|  | 	var formats = make([]*formatCacheV1, diskCount) | ||
|  | 
 | ||
|  | 	for i := 0; i < diskCount; i++ { | ||
|  | 		format := &formatCacheV1{} | ||
|  | 		format.Version = formatMetaVersion1 | ||
|  | 		format.Format = formatCache | ||
|  | 		format.Cache.Version = formatCacheVersionV1 | ||
|  | 		format.Cache.This = mustGetUUID() | ||
|  | 		formats[i] = format | ||
|  | 		disks[i] = formats[i].Cache.This | ||
|  | 	} | ||
|  | 	for i := 0; i < diskCount; i++ { | ||
|  | 		format := formats[i] | ||
|  | 		format.Cache.Disks = disks | ||
|  | 	} | ||
|  | 	return formats | ||
|  | } | ||
|  | 
 | ||
|  | // Returns format.Cache.Version
 | ||
|  | func formatCacheGetVersion(r io.ReadSeeker) (string, error) { | ||
|  | 	format := &formatCacheVersionDetect{} | ||
|  | 	if err := jsonLoad(r, format); err != nil { | ||
|  | 		return "", err | ||
|  | 	} | ||
|  | 	return format.Cache.Version, nil | ||
|  | } | ||
|  | 
 | ||
|  | // Creates a new cache format.json if unformatted.
 | ||
|  | func createFormatCache(fsFormatPath string, format *formatCacheV1) error { | ||
|  | 	// open file using READ & WRITE permission
 | ||
|  | 	var file, err = os.OpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0600) | ||
|  | 	if err != nil { | ||
|  | 		return errors2.Trace(err) | ||
|  | 	} | ||
|  | 	// Close the locked file upon return.
 | ||
|  | 	defer file.Close() | ||
|  | 
 | ||
|  | 	fi, err := file.Stat() | ||
|  | 	if err != nil { | ||
|  | 		return errors2.Trace(err) | ||
|  | 	} | ||
|  | 	if fi.Size() != 0 { | ||
|  | 		// format.json already got created because of another minio process's createFormatCache()
 | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	return jsonSave(file, format) | ||
|  | } | ||
|  | 
 | ||
|  | // This function creates a cache format file on disk and returns a slice
 | ||
|  | // of format cache config
 | ||
|  | func initFormatCache(drives []string) (formats []*formatCacheV1, err error) { | ||
|  | 	nformats := newFormatCacheV1(drives) | ||
|  | 	for i, drive := range drives { | ||
|  | 		// Disallow relative paths, figure out absolute paths.
 | ||
|  | 		cfsPath, err := filepath.Abs(drive) | ||
|  | 		if err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 
 | ||
|  | 		fi, err := os.Stat(cfsPath) | ||
|  | 		if err == nil { | ||
|  | 			if !fi.IsDir() { | ||
|  | 				return nil, syscall.ENOTDIR | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if os.IsNotExist(err) { | ||
|  | 			// Disk not found create it.
 | ||
|  | 			err = os.MkdirAll(cfsPath, 0777) | ||
|  | 			if err != nil { | ||
|  | 				return nil, err | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		cacheFormatPath := pathJoin(drive, formatConfigFile) | ||
|  | 		// Fresh disk - create format.json for this cfs
 | ||
|  | 		if err = createFormatCache(cacheFormatPath, nformats[i]); err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nformats, nil | ||
|  | } | ||
|  | 
 | ||
|  | func loadFormatCache(drives []string) (formats []*formatCacheV1, err error) { | ||
|  | 	var errs []error | ||
|  | 	for _, drive := range drives { | ||
|  | 		cacheFormatPath := pathJoin(drive, formatConfigFile) | ||
|  | 		f, perr := os.Open(cacheFormatPath) | ||
|  | 		if perr != nil { | ||
|  | 			formats = append(formats, nil) | ||
|  | 			errs = append(errs, perr) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		defer f.Close() | ||
|  | 		format, perr := formatMetaCacheV1(f) | ||
|  | 		if perr != nil { | ||
|  | 			// format could not be unmarshalled.
 | ||
|  | 			formats = append(formats, nil) | ||
|  | 			errs = append(errs, perr) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		formats = append(formats, format) | ||
|  | 	} | ||
|  | 	for _, perr := range errs { | ||
|  | 		if perr != nil { | ||
|  | 			err = perr | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return formats, err | ||
|  | } | ||
|  | 
 | ||
|  | // unmarshalls the cache format.json into formatCacheV1
 | ||
|  | func formatMetaCacheV1(r io.ReadSeeker) (*formatCacheV1, error) { | ||
|  | 	format := &formatCacheV1{} | ||
|  | 	if err := jsonLoad(r, format); err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	return format, nil | ||
|  | } | ||
|  | 
 | ||
|  | func checkFormatCacheValue(format *formatCacheV1) error { | ||
|  | 	// Validate format version and format type.
 | ||
|  | 	if format.Version != formatMetaVersion1 { | ||
|  | 		return fmt.Errorf("Unsupported version of cache format [%s] found", format.Version) | ||
|  | 	} | ||
|  | 	if format.Format != formatCache { | ||
|  | 		return fmt.Errorf("Unsupported cache format [%s] found", format.Format) | ||
|  | 	} | ||
|  | 	if format.Cache.Version != formatCacheVersionV1 { | ||
|  | 		return fmt.Errorf("Unsupported Cache backend format found [%s]", format.Cache.Version) | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func checkFormatCacheValues(formats []*formatCacheV1) (int, error) { | ||
|  | 
 | ||
|  | 	for i, formatCache := range formats { | ||
|  | 		if formatCache == nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		if err := checkFormatCacheValue(formatCache); err != nil { | ||
|  | 			return i, err | ||
|  | 		} | ||
|  | 		if len(formats) != len(formatCache.Cache.Disks) { | ||
|  | 			return i, fmt.Errorf("Expected number of cache drives %d , got  %d", | ||
|  | 				len(formatCache.Cache.Disks), len(formats)) | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return -1, nil | ||
|  | } | ||
|  | 
 | ||
|  | // checkCacheDisksConsistency - checks if "This" disk uuid on each disk is consistent with all "Disks" slices
 | ||
|  | // across disks.
 | ||
|  | func checkCacheDiskConsistency(formats []*formatCacheV1) error { | ||
|  | 	var disks = make([]string, len(formats)) | ||
|  | 	// Collect currently available disk uuids.
 | ||
|  | 	for index, format := range formats { | ||
|  | 		if format == nil { | ||
|  | 			disks[index] = "" | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		disks[index] = format.Cache.This | ||
|  | 	} | ||
|  | 	for i, format := range formats { | ||
|  | 		if format == nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		j := findCacheDiskIndex(disks[i], format.Cache.Disks) | ||
|  | 		if j == -1 { | ||
|  | 			return fmt.Errorf("UUID on positions %d:%d do not match with , expected %s", i, j, disks[i]) | ||
|  | 		} | ||
|  | 		if i != j { | ||
|  | 			return fmt.Errorf("UUID on positions %d:%d do not match with , expected %s got %s", i, j, disks[i], format.Cache.Disks[j]) | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | // checkCacheDisksSliceConsistency - validate cache Disks order if they are consistent.
 | ||
|  | func checkCacheDisksSliceConsistency(formats []*formatCacheV1) error { | ||
|  | 	var sentinelDisks []string | ||
|  | 	// Extract first valid Disks slice.
 | ||
|  | 	for _, format := range formats { | ||
|  | 		if format == nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		sentinelDisks = format.Cache.Disks | ||
|  | 		break | ||
|  | 	} | ||
|  | 	for _, format := range formats { | ||
|  | 		if format == nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		currentDisks := format.Cache.Disks | ||
|  | 		if !reflect.DeepEqual(sentinelDisks, currentDisks) { | ||
|  | 			return errors.New("inconsistent cache drives found") | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | // findCacheDiskIndex returns position of cache disk in JBOD.
 | ||
|  | func findCacheDiskIndex(disk string, disks []string) int { | ||
|  | 	for index, uuid := range disks { | ||
|  | 		if uuid == disk { | ||
|  | 			return index | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return -1 | ||
|  | } | ||
|  | 
 | ||
|  | // validate whether cache drives order has changed
 | ||
|  | func validateCacheFormats(formats []*formatCacheV1) error { | ||
|  | 	if _, err := checkFormatCacheValues(formats); err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	if err := checkCacheDisksSliceConsistency(formats); err != nil { | ||
|  | 		return err | ||
|  | 	} | ||
|  | 	return checkCacheDiskConsistency(formats) | ||
|  | } | ||
|  | 
 | ||
|  | // return true if all of the list of cache drives are
 | ||
|  | // fresh disks
 | ||
|  | func cacheDrivesUnformatted(drives []string) bool { | ||
|  | 	count := 0 | ||
|  | 	for _, drive := range drives { | ||
|  | 		cacheFormatPath := pathJoin(drive, formatConfigFile) | ||
|  | 
 | ||
|  | 		// // Disallow relative paths, figure out absolute paths.
 | ||
|  | 		cfsPath, err := filepath.Abs(cacheFormatPath) | ||
|  | 		if err != nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 
 | ||
|  | 		fi, err := os.Stat(cfsPath) | ||
|  | 		if err == nil { | ||
|  | 			if !fi.IsDir() { | ||
|  | 				continue | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if os.IsNotExist(err) { | ||
|  | 			count++ | ||
|  | 			continue | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return count == len(drives) | ||
|  | } | ||
|  | 
 | ||
|  | // create format.json for each cache drive if fresh disk or load format from disk
 | ||
|  | // Then validate the format for all drives in the cache to ensure order
 | ||
|  | // of cache drives has not changed.
 | ||
|  | func loadAndValidateCacheFormat(drives []string) (formats []*formatCacheV1, err error) { | ||
|  | 	if cacheDrivesUnformatted(drives) { | ||
|  | 		formats, err = initFormatCache(drives) | ||
|  | 	} else { | ||
|  | 		formats, err = loadFormatCache(drives) | ||
|  | 	} | ||
|  | 	if err != nil { | ||
|  | 		return formats, err | ||
|  | 	} | ||
|  | 	return formats, validateCacheFormats(formats) | ||
|  | } |