aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2021-04-26 21:54:09 -0700
committerGitHub <noreply@github.com>2021-04-26 21:54:09 -0700
commit400c8ef8d88eae27a1abbef3da0badd8ef1bab6d (patch)
tree621d6f4f432cefe75c455939e084193eb7462001
parent86185262bb86e31f9a2f71e85d02df2502c7ad40 (diff)
parentcf552417a7a422d1313c53972fd1175684e758e0 (diff)
downloadseaweedfs-400c8ef8d88eae27a1abbef3da0badd8ef1bab6d.tar.xz
seaweedfs-400c8ef8d88eae27a1abbef3da0badd8ef1bab6d.zip
Merge pull request #2025 from bingoohuang/master
improvement for minFreeSpace argument
-rw-r--r--unmaintained/repeated_vacuum/repeated_vacuum.go2
-rw-r--r--weed/command/server.go9
-rw-r--r--weed/command/volume.go28
-rw-r--r--weed/server/volume_server.go4
-rw-r--r--weed/storage/disk_location.go18
-rw-r--r--weed/storage/store.go6
-rw-r--r--weed/util/bytes.go95
-rw-r--r--weed/util/bytes_test.go59
-rw-r--r--weed/util/minfreespace.go90
-rw-r--r--weed/util/minfreespace_test.go29
10 files changed, 305 insertions, 35 deletions
diff --git a/unmaintained/repeated_vacuum/repeated_vacuum.go b/unmaintained/repeated_vacuum/repeated_vacuum.go
index bff5becc1..d85e45af0 100644
--- a/unmaintained/repeated_vacuum/repeated_vacuum.go
+++ b/unmaintained/repeated_vacuum/repeated_vacuum.go
@@ -52,7 +52,7 @@ func main() {
}
func genFile(grpcDialOption grpc.DialOption, i int) (*operation.AssignResult, string) {
- assignResult, err := operation.Assign(*master, grpcDialOption, &operation.VolumeAssignRequest{
+ assignResult, err := operation.Assign(func() string { return *master }, grpcDialOption, &operation.VolumeAssignRequest{
Count: 1,
Replication: *replication,
})
diff --git a/weed/command/server.go b/weed/command/server.go
index 6eb3bf97c..d0020d33b 100644
--- a/weed/command/server.go
+++ b/weed/command/server.go
@@ -58,7 +58,8 @@ var (
serverDisableHttp = cmdServer.Flag.Bool("disableHttp", false, "disable http requests, only gRPC operations are allowed.")
volumeDataFolders = cmdServer.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
volumeMaxDataVolumeCounts = cmdServer.Flag.String("volume.max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.")
- volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.")
+ volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).")
+ volumeMinFreeSpace = cmdServer.Flag.String("volume.minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.")
serverMetricsHttpPort = cmdServer.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
// pulseSeconds = cmdServer.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats")
@@ -244,8 +245,8 @@ func runServer(cmd *Command, args []string) bool {
// start volume server
if *isStartingVolumeServer {
- go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, *volumeMinFreeSpacePercent)
-
+ minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent)
+ go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, minFreeSpaces)
}
if *isStartingMasterServer {
@@ -253,6 +254,4 @@ func runServer(cmd *Command, args []string) bool {
}
select {}
-
- return true
}
diff --git a/weed/command/volume.go b/weed/command/volume.go
index 9df500178..139a3791e 100644
--- a/weed/command/volume.go
+++ b/weed/command/volume.go
@@ -57,7 +57,6 @@ type VolumeServerOptions struct {
compactionMBPerSecond *int
fileSizeLimitMB *int
concurrentUploadLimitMB *int
- minFreeSpacePercents []float32
pprof *bool
preStopSeconds *int
metricsHttpPort *int
@@ -105,7 +104,8 @@ var (
volumeFolders = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
maxVolumeCounts = cmdVolume.Flag.String("max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.")
volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
- minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.")
+ minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).")
+ minFreeSpace = cmdVolume.Flag.String("minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.")
)
func runVolume(cmd *Command, args []string) bool {
@@ -120,12 +120,13 @@ func runVolume(cmd *Command, args []string) bool {
go stats_collect.StartMetricsServer(*v.metricsHttpPort)
- v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, *minFreeSpacePercent)
+ minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent)
+ v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, minFreeSpaces)
return true
}
-func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption, minFreeSpacePercent string) {
+func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption string, minFreeSpaces []util.MinFreeSpace) {
// Set multiple folders and each folder's max volume count limit'
v.folders = strings.Split(volumeFolders, ",")
@@ -153,22 +154,13 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
}
- // set minFreeSpacePercent
- minFreeSpacePercentStrings := strings.Split(minFreeSpacePercent, ",")
- for _, freeString := range minFreeSpacePercentStrings {
- if value, e := strconv.ParseFloat(freeString, 32); e == nil {
- v.minFreeSpacePercents = append(v.minFreeSpacePercents, float32(value))
- } else {
- glog.Fatalf("The value specified in -minFreeSpacePercent not a valid value %s", freeString)
- }
- }
- if len(v.minFreeSpacePercents) == 1 && len(v.folders) > 1 {
+ if len(minFreeSpaces) == 1 && len(v.folders) > 1 {
for i := 0; i < len(v.folders)-1; i++ {
- v.minFreeSpacePercents = append(v.minFreeSpacePercents, v.minFreeSpacePercents[0])
+ minFreeSpaces = append(minFreeSpaces, minFreeSpaces[0])
}
}
- if len(v.folders) != len(v.minFreeSpacePercents) {
- glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(v.minFreeSpacePercents))
+ if len(v.folders) != len(minFreeSpaces) {
+ glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(minFreeSpaces))
}
// set disk types
@@ -231,7 +223,7 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
volumeServer := weed_server.NewVolumeServer(volumeMux, publicVolumeMux,
*v.ip, *v.port, *v.publicUrl,
- v.folders, v.folderMaxLimits, v.minFreeSpacePercents, diskTypes,
+ v.folders, v.folderMaxLimits, minFreeSpaces, diskTypes,
*v.idxFolder,
volumeNeedleMapKind,
strings.Split(masters, ","), 5, *v.dataCenter, *v.rack,
diff --git a/weed/server/volume_server.go b/weed/server/volume_server.go
index e11d607a4..f7359ea6b 100644
--- a/weed/server/volume_server.go
+++ b/weed/server/volume_server.go
@@ -43,7 +43,7 @@ type VolumeServer struct {
func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
port int, publicUrl string,
- folders []string, maxCounts []int, minFreeSpacePercents []float32, diskTypes []types.DiskType,
+ folders []string, maxCounts []int, minFreeSpaces []util.MinFreeSpace, diskTypes []types.DiskType,
idxFolder string,
needleMapKind storage.NeedleMapKind,
masterNodes []string, pulseSeconds int,
@@ -85,7 +85,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
vs.checkWithMaster()
- vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpacePercents, idxFolder, vs.needleMapKind, diskTypes)
+ vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpaces, idxFolder, vs.needleMapKind, diskTypes)
vs.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec)
handleStaticResources(adminMux)
diff --git a/weed/storage/disk_location.go b/weed/storage/disk_location.go
index ed4e00312..33dd272ce 100644
--- a/weed/storage/disk_location.go
+++ b/weed/storage/disk_location.go
@@ -23,7 +23,7 @@ type DiskLocation struct {
DiskType types.DiskType
MaxVolumeCount int
OriginalMaxVolumeCount int
- MinFreeSpacePercent float32
+ MinFreeSpace util.MinFreeSpace
volumes map[needle.VolumeId]*Volume
volumesLock sync.RWMutex
@@ -34,7 +34,7 @@ type DiskLocation struct {
isDiskSpaceLow bool
}
-func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32, idxDir string, diskType types.DiskType) *DiskLocation {
+func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpace util.MinFreeSpace, idxDir string, diskType types.DiskType) *DiskLocation {
dir = util.ResolvePath(dir)
if idxDir == "" {
idxDir = dir
@@ -47,7 +47,7 @@ func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32
DiskType: diskType,
MaxVolumeCount: maxVolumeCount,
OriginalMaxVolumeCount: maxVolumeCount,
- MinFreeSpacePercent: minFreeSpacePercent,
+ MinFreeSpace: minFreeSpace,
}
location.volumes = make(map[needle.VolumeId]*Volume)
location.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume)
@@ -361,14 +361,18 @@ func (l *DiskLocation) CheckDiskSpace() {
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "all").Set(float64(s.All))
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "used").Set(float64(s.Used))
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "free").Set(float64(s.Free))
- if (s.PercentFree < l.MinFreeSpacePercent) != l.isDiskSpaceLow {
+
+ isLow, desc := l.MinFreeSpace.IsLow(s.Free, s.PercentFree)
+ if isLow != l.isDiskSpaceLow {
l.isDiskSpaceLow = !l.isDiskSpaceLow
}
+
+ logLevel := glog.Level(4)
if l.isDiskSpaceLow {
- glog.V(0).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow)
- } else {
- glog.V(4).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow)
+ logLevel = glog.Level(0)
}
+
+ glog.V(logLevel).Infof("dir %s %s", dir, desc)
}
time.Sleep(time.Minute)
}
diff --git a/weed/storage/store.go b/weed/storage/store.go
index 6be15a4c9..f27f2412f 100644
--- a/weed/storage/store.go
+++ b/weed/storage/store.go
@@ -2,6 +2,7 @@ package storage
import (
"fmt"
+ "github.com/chrislusf/seaweedfs/weed/util"
"path/filepath"
"strings"
"sync/atomic"
@@ -52,11 +53,12 @@ func (s *Store) String() (str string) {
return
}
-func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, minFreeSpacePercents []float32, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) {
+func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int,
+ minFreeSpaces []util.MinFreeSpace, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) {
s = &Store{grpcDialOption: grpcDialOption, Port: port, Ip: ip, PublicUrl: publicUrl, NeedleMapKind: needleMapKind}
s.Locations = make([]*DiskLocation, 0)
for i := 0; i < len(dirnames); i++ {
- location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpacePercents[i], idxFolder, diskTypes[i])
+ location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpaces[i], idxFolder, diskTypes[i])
location.loadExistingVolumes(needleMapKind)
s.Locations = append(s.Locations, location)
stats.VolumeServerMaxVolumeCounter.Add(float64(maxVolumeCounts[i]))
diff --git a/weed/util/bytes.go b/weed/util/bytes.go
index c2a4df108..26da91033 100644
--- a/weed/util/bytes.go
+++ b/weed/util/bytes.go
@@ -7,6 +7,10 @@ import (
"encoding/base64"
"fmt"
"io"
+ "math"
+ "strconv"
+ "strings"
+ "unicode"
)
// BytesToHumanReadable returns the converted human readable representation of the bytes.
@@ -161,3 +165,94 @@ func NewBytesReader(b []byte) *BytesReader {
Reader: bytes.NewReader(b),
}
}
+
+// EmptyTo returns to if s is empty.
+func EmptyTo(s, to string) string {
+ if s == "" {
+ return to
+ }
+
+ return s
+}
+
+// IfElse works like b ? this : that.
+func IfElse(b bool, this, that string) string {
+ if b {
+ return this
+ }
+ return that
+}
+
+// ParseBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See Also: Bytes, IBytes.
+//
+// ParseBytes("42MB") -> 42000000, nil
+// ParseBytes("42 MB") -> 42000000, nil
+// ParseBytes("42 mib") -> 44040192, nil
+func ParseBytes(s string) (uint64, error) {
+ lastDigit := 0
+ hasComma := false
+ for _, r := range s {
+ if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+ break
+ }
+ if r == ',' {
+ hasComma = true
+ }
+ lastDigit++
+ }
+
+ num := s[:lastDigit]
+ if hasComma {
+ num = strings.Replace(num, ",", "", -1)
+ }
+
+ f, err := strconv.ParseFloat(num, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+ if m, ok := bytesSizeTable[extra]; ok {
+ f *= float64(m)
+ if f >= math.MaxUint64 {
+ return 0, fmt.Errorf("too large: %v", s)
+ }
+ return uint64(f), nil
+ }
+
+ return 0, fmt.Errorf("unhandled size name: %v", extra)
+}
+
+var bytesSizeTable = map[string]uint64{
+ "b": Byte, "kib": KiByte, "kb": KByte, "mib": MiByte, "mb": MByte, "gib": GiByte, "gb": GByte,
+ "tib": TiByte, "tb": TByte, "pib": PiByte, "pb": PByte, "eib": EiByte, "eb": EByte,
+ // Without suffix
+ "": Byte, "ki": KiByte, "k": KByte, "mi": MiByte, "m": MByte, "gi": GiByte, "g": GByte,
+ "ti": TiByte, "t": TByte, "pi": PiByte, "p": PByte, "ei": EiByte, "e": EByte,
+}
+
+// IEC Sizes.
+// kibis of bits
+const (
+ Byte = 1 << (iota * 10)
+ KiByte
+ MiByte
+ GiByte
+ TiByte
+ PiByte
+ EiByte
+)
+
+// SI Sizes.
+const (
+ IByte = 1
+ KByte = IByte * 1000
+ MByte = KByte * 1000
+ GByte = MByte * 1000
+ TByte = GByte * 1000
+ PByte = TByte * 1000
+ EByte = PByte * 1000
+)
diff --git a/weed/util/bytes_test.go b/weed/util/bytes_test.go
new file mode 100644
index 000000000..d9269cadb
--- /dev/null
+++ b/weed/util/bytes_test.go
@@ -0,0 +1,59 @@
+package util
+
+import "testing"
+
+func TestByteParsing(t *testing.T) {
+ tests := []struct {
+ in string
+ exp uint64
+ }{
+ {"42", 42},
+ {"42MB", 42000000},
+ {"42MiB", 44040192},
+ {"42mb", 42000000},
+ {"42mib", 44040192},
+ {"42MIB", 44040192},
+ {"42 MB", 42000000},
+ {"42 MiB", 44040192},
+ {"42 mb", 42000000},
+ {"42 mib", 44040192},
+ {"42 MIB", 44040192},
+ {"42.5MB", 42500000},
+ {"42.5MiB", 44564480},
+ {"42.5 MB", 42500000},
+ {"42.5 MiB", 44564480},
+ // No need to say B
+ {"42M", 42000000},
+ {"42Mi", 44040192},
+ {"42m", 42000000},
+ {"42mi", 44040192},
+ {"42MI", 44040192},
+ {"42 M", 42000000},
+ {"42 Mi", 44040192},
+ {"42 m", 42000000},
+ {"42 mi", 44040192},
+ {"42 MI", 44040192},
+ {"42.5M", 42500000},
+ {"42.5Mi", 44564480},
+ {"42.5 M", 42500000},
+ {"42.5 Mi", 44564480},
+ // Bug #42
+ {"1,005.03 MB", 1005030000},
+ // Large testing, breaks when too much larger than
+ // this.
+ {"12.5 EB", uint64(12.5 * float64(EByte))},
+ {"12.5 E", uint64(12.5 * float64(EByte))},
+ {"12.5 EiB", uint64(12.5 * float64(EiByte))},
+ }
+
+ for _, p := range tests {
+ got, err := ParseBytes(p.in)
+ if err != nil {
+ t.Errorf("Couldn't parse %v: %v", p.in, err)
+ }
+ if got != p.exp {
+ t.Errorf("Expected %v for %v, got %v",
+ p.exp, p.in, got)
+ }
+ }
+}
diff --git a/weed/util/minfreespace.go b/weed/util/minfreespace.go
new file mode 100644
index 000000000..c802bf6dd
--- /dev/null
+++ b/weed/util/minfreespace.go
@@ -0,0 +1,90 @@
+package util
+
+import (
+ "errors"
+ "fmt"
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ "strconv"
+ "strings"
+)
+
+// MinFreeSpaceType is the type of MinFreeSpace.
+type MinFreeSpaceType int
+
+const (
+ // AsPercent set the MinFreeSpaceType to a percentage value from 0 to 100.
+ AsPercent MinFreeSpaceType = iota
+ // AsBytes set the MinFreeSpaceType to a absolute value bytes.
+ AsBytes
+)
+
+// MinFreeSpace is type that defines the limit for the minimum free space.
+type MinFreeSpace struct {
+ Type MinFreeSpaceType
+ Bytes uint64
+ Percent float32
+ Raw string
+}
+
+// IsLow tells whether the free space is low or not.
+func (s MinFreeSpace) IsLow(freeBytes uint64, freePercent float32) (yes bool, desc string) {
+ switch s.Type {
+ case AsPercent:
+ yes = freePercent < s.Percent
+ op := IfElse(yes, "<", ">=")
+ return yes, fmt.Sprintf("disk free %.2f%% %s required %.2f%%", freePercent, op, s.Percent)
+ case AsBytes:
+ yes = freeBytes < s.Bytes
+ op := IfElse(yes, "<", ">=")
+ return yes, fmt.Sprintf("disk free %s %s required %s",
+ BytesToHumanReadable(freeBytes), op, BytesToHumanReadable(s.Bytes))
+ }
+
+ return false, ""
+}
+
+// String returns a string representation of MinFreeSpace.
+func (s MinFreeSpace) String() string {
+ switch s.Type {
+ case AsPercent:
+ return fmt.Sprintf("%.2f%%", s.Percent)
+ default:
+ return s.Raw
+ }
+}
+
+// MustParseMinFreeSpace parses comma-separated argument for min free space setting.
+// minFreeSpace has the high priority than minFreeSpacePercent if it is set.
+func MustParseMinFreeSpace(minFreeSpace string, minFreeSpacePercent string) (spaces []MinFreeSpace) {
+ ss := strings.Split(EmptyTo(minFreeSpace, minFreeSpacePercent), ",")
+ for _, freeString := range ss {
+ if vv, e := ParseMinFreeSpace(freeString); e == nil {
+ spaces = append(spaces, *vv)
+ } else {
+ glog.Fatalf("The value specified in -minFreeSpace not a valid value %s", freeString)
+ }
+ }
+
+ return spaces
+}
+
+var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid")
+
+// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G
+func ParseMinFreeSpace(s string) (*MinFreeSpace, error) {
+ if percent, e := strconv.ParseFloat(s, 32); e == nil {
+ if percent < 0 || percent > 100 {
+ return nil, ErrMinFreeSpaceBadValue
+ }
+ return &MinFreeSpace{Type: AsPercent, Percent: float32(percent), Raw: s}, nil
+ }
+
+ if directSize, e := ParseBytes(s); e == nil {
+ if directSize <= 100 {
+ return nil, ErrMinFreeSpaceBadValue
+ }
+ return &MinFreeSpace{Type: AsBytes, Bytes: directSize, Raw: s}, nil
+ }
+
+ return nil, ErrMinFreeSpaceBadValue
+}
diff --git a/weed/util/minfreespace_test.go b/weed/util/minfreespace_test.go
new file mode 100644
index 000000000..eec1942dd
--- /dev/null
+++ b/weed/util/minfreespace_test.go
@@ -0,0 +1,29 @@
+package util
+
+import "testing"
+
+func TestParseMinFreeSpace(t *testing.T) {
+ tests := []struct {
+ in string
+ ok bool
+ value *MinFreeSpace
+ }{
+ {in: "42", ok: true, value: &MinFreeSpace{Type: AsPercent, Percent: 42, Raw: "42"}},
+ {in: "-1", ok: false, value: nil},
+ {in: "101", ok: false, value: nil},
+ {in: "100B", ok: false, value: nil},
+ {in: "100Ki", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024, Raw: "100Ki"}},
+ {in: "100GiB", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024 * 1024 * 1024, Raw: "100GiB"}},
+ {in: "42M", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 42 * 1000 * 1000, Raw: "42M"}},
+ }
+
+ for _, p := range tests {
+ got, err := ParseMinFreeSpace(p.in)
+ if p.ok != (err == nil) {
+ t.Errorf("failed to test %v", p.in)
+ }
+ if p.ok && err == nil && *got != *p.value {
+ t.Errorf("failed to test %v", p.in)
+ }
+ }
+}