posix: Check for min disk space and inodes (#4618)

This is needed such that we don't start or
allow writing to a posix disk which doesn't
have minimum total disk space available.

One part fix for #4617
master
Harshavardhana 7 years ago committed by Dee Koder
parent ce403fdaa0
commit cc8a8cb877
  1. 16
      cmd/fs-v1.go
  2. 89
      cmd/posix.go
  3. 105
      cmd/posix_test.go

@ -103,6 +103,16 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) {
} }
} }
di, err := getDiskInfo(preparePath(fsPath))
if err != nil {
return nil, err
}
// Check if disk has minimum required total space.
if err = checkDiskMinTotal(di); err != nil {
return nil, err
}
// Assign a new UUID for FS minio mode. Each server instance // Assign a new UUID for FS minio mode. Each server instance
// gets its own UUID for temporary file transaction. // gets its own UUID for temporary file transaction.
fsUUID := mustGetUUID() fsUUID := mustGetUUID()
@ -138,14 +148,12 @@ func newFSObjectLayer(fsPath string) (ObjectLayer, error) {
fs.fsFormatRlk = rlk fs.fsFormatRlk = rlk
// Initialize and load bucket policies. // Initialize and load bucket policies.
err = initBucketPolicies(fs) if err = initBucketPolicies(fs); err != nil {
if err != nil {
return nil, fmt.Errorf("Unable to load all bucket policies. %s", err) return nil, fmt.Errorf("Unable to load all bucket policies. %s", err)
} }
// Initialize a new event notifier. // Initialize a new event notifier.
err = initEventNotifier(fs) if err = initEventNotifier(fs); err != nil {
if err != nil {
return nil, fmt.Errorf("Unable to initialize event notification. %s", err) return nil, fmt.Errorf("Unable to initialize event notification. %s", err)
} }

@ -35,9 +35,11 @@ import (
) )
const ( const (
fsMinFreeSpace = 1 * humanize.GiByte // Min 1GiB free space. diskMinFreeSpace = 1 * humanize.GiByte // Min 1GiB free space.
fsMinFreeInodes = 10000 // Min 10000. diskMinTotalSpace = diskMinFreeSpace // Min 1GiB total space.
maxAllowedIOError = 5 diskMinFreeInodes = 10000 // Min 10000 free inodes.
diskMinTotalInodes = diskMinFreeInodes // Min 10000 total inodes.
maxAllowedIOError = 5
) )
// posix - implements StorageAPI interface. // posix - implements StorageAPI interface.
@ -108,7 +110,7 @@ func newPosix(path string) (StorageAPI, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
fs := &posix{ st := &posix{
diskPath: diskPath, diskPath: diskPath,
// 1MiB buffer pool for posix internal operations. // 1MiB buffer pool for posix internal operations.
pool: sync.Pool{ pool: sync.Pool{
@ -131,7 +133,19 @@ func newPosix(path string) (StorageAPI, error) {
return nil, err return nil, err
} }
} }
return fs, nil
di, err := getDiskInfo(preparePath(diskPath))
if err != nil {
return nil, err
}
// Check if disk has minimum required total space.
if err = checkDiskMinTotal(di); err != nil {
return nil, err
}
// Success.
return st, nil
} }
// getDiskInfo returns given disk information. // getDiskInfo returns given disk information.
@ -155,44 +169,77 @@ var ignoreDiskFreeOS = []string{
globalSolarisOSName, globalSolarisOSName,
} }
// checkDiskFree verifies if disk path has sufficient minimum free disk space and files. // check if disk total has minimum required size.
func checkDiskFree(diskPath string, neededSpace int64) (err error) { func checkDiskMinTotal(di disk.Info) (err error) {
// We don't validate disk space or inode utilization on windows. // Remove 5% from total space for cumulative disk space
// Each windows calls to 'GetVolumeInformationW' takes around 3-5seconds. // used for journalling, inodes etc.
// And StatFS is not supported by Go for solaris and netbsd. totalDiskSpace := float64(di.Total) * 0.95
if contains(ignoreDiskFreeOS, runtime.GOOS) { if int64(totalDiskSpace) <= diskMinTotalSpace {
return nil return errDiskFull
} }
var di disk.Info // Some filesystems do not implement a way to provide total inodes available, instead
di, err = getDiskInfo(preparePath(diskPath)) // inodes are allocated based on available disk space. For example CephDISK, StoreNext CVDISK,
if err != nil { // AzureFile driver. Allow for the available disk to be separately validated and we will
return err // validate inodes only if total inodes are provided by the underlying filesystem.
if di.Files != 0 && di.FSType != "NFS" {
totalFiles := int64(di.Files)
if totalFiles <= diskMinTotalInodes {
return errDiskFull
}
} }
return nil
}
// check if disk free has minimum required size.
func checkDiskMinFree(di disk.Info) error {
// Remove 5% from free space for cumulative disk space used for journalling, inodes etc. // Remove 5% from free space for cumulative disk space used for journalling, inodes etc.
availableDiskSpace := float64(di.Free) * 0.95 availableDiskSpace := float64(di.Free) * 0.95
if int64(availableDiskSpace) <= fsMinFreeSpace { if int64(availableDiskSpace) <= diskMinFreeSpace {
return errDiskFull return errDiskFull
} }
// Some filesystems do not implement a way to provide total inodes available, instead inodes // Some filesystems do not implement a way to provide total inodes available, instead inodes
// are allocated based on available disk space. For example CephFS, StoreNext CVFS, AzureFile driver. // are allocated based on available disk space. For example CephDISK, StoreNext CVDISK, AzureFile driver.
// Allow for the available disk to be separately validate and we will validate inodes only if // Allow for the available disk to be separately validate and we will validate inodes only if
// total inodes are provided by the underlying filesystem. // total inodes are provided by the underlying filesystem.
if di.Files != 0 && di.FSType != "NFS" { if di.Files != 0 && di.FSType != "NFS" {
availableFiles := int64(di.Ffree) availableFiles := int64(di.Ffree)
if availableFiles <= fsMinFreeInodes { if availableFiles <= diskMinFreeInodes {
return errDiskFull return errDiskFull
} }
} }
// Success.
return nil
}
// checkDiskFree verifies if disk path has sufficient minimum free disk space and files.
func checkDiskFree(diskPath string, neededSpace int64) (err error) {
// We don't validate disk space or inode utilization on windows.
// Each windows call to 'GetVolumeInformationW' takes around
// 3-5seconds. And StatDISK is not supported by Go for solaris
// and netbsd.
if contains(ignoreDiskFreeOS, runtime.GOOS) {
return nil
}
var di disk.Info
di, err = getDiskInfo(preparePath(diskPath))
if err != nil {
return err
}
if err = checkDiskMinFree(di); err != nil {
return err
}
// Check if we have enough space to store data // Check if we have enough space to store data
if neededSpace > int64(availableDiskSpace) { if neededSpace > int64(float64(di.Free)*0.95) {
return errDiskFull return errDiskFull
} }
// Success.
return nil return nil
} }

@ -29,6 +29,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/minio/minio/pkg/disk"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
) )
@ -1669,3 +1671,106 @@ func TestPosixStatFile(t *testing.T) {
} }
} }
} }
// Checks for restrictions for min total disk space and inodes.
func TestCheckDiskTotalMin(t *testing.T) {
testCases := []struct {
diskInfo disk.Info
err error
}{
// Test 1 - when fstype is nfs.
{
diskInfo: disk.Info{
Total: diskMinTotalSpace * 3,
FSType: "NFS",
},
err: nil,
},
// Test 2 - when fstype is xfs and total inodes are small.
{
diskInfo: disk.Info{
Total: diskMinTotalSpace * 3,
FSType: "XFS",
Files: 9999,
},
err: errDiskFull,
},
// Test 3 - when fstype is btrfs and total inodes is empty.
{
diskInfo: disk.Info{
Total: diskMinTotalSpace * 3,
FSType: "BTRFS",
Files: 0,
},
err: nil,
},
// Test 4 - when fstype is xfs and total disk space is really small.
{
diskInfo: disk.Info{
Total: diskMinTotalSpace - diskMinTotalSpace/1024,
FSType: "XFS",
Files: 9999,
},
err: errDiskFull,
},
}
// Validate all cases.
for i, test := range testCases {
if err := checkDiskMinTotal(test.diskInfo); test.err != err {
t.Errorf("Test %d: Expected error %s, got %s", i+1, test.err, err)
}
}
}
// Checks for restrictions for min free disk space and inodes.
func TestCheckDiskFreeMin(t *testing.T) {
testCases := []struct {
diskInfo disk.Info
err error
}{
// Test 1 - when fstype is nfs.
{
diskInfo: disk.Info{
Free: diskMinTotalSpace * 3,
FSType: "NFS",
},
err: nil,
},
// Test 2 - when fstype is xfs and total inodes are small.
{
diskInfo: disk.Info{
Free: diskMinTotalSpace * 3,
FSType: "XFS",
Files: 9999,
Ffree: 9999,
},
err: errDiskFull,
},
// Test 3 - when fstype is btrfs and total inodes are empty.
{
diskInfo: disk.Info{
Free: diskMinTotalSpace * 3,
FSType: "BTRFS",
Files: 0,
},
err: nil,
},
// Test 4 - when fstype is xfs and total disk space is really small.
{
diskInfo: disk.Info{
Free: diskMinTotalSpace - diskMinTotalSpace/1024,
FSType: "XFS",
Files: 9999,
},
err: errDiskFull,
},
}
// Validate all cases.
for i, test := range testCases {
if err := checkDiskMinFree(test.diskInfo); test.err != err {
t.Errorf("Test %d: Expected error %s, got %s", i+1, test.err, err)
}
}
}

Loading…
Cancel
Save