Repository: nats-io/nats-server (go)

Issue:
The "max_file_store" option cannot be quoted
## Defect

The server does not process a quoted string of the `max_file_store` option.  It'd be great it if could.

```bash
$ cat quoted.conf 
jetstream {
  max_file_store: "200M"
}

$ nats-server -c quoted.conf
nats-server: quoted.conf:1:0: interface conversion: interface {} is string, not int64
```

Others like this should be checked as well...



Pull request description:
[ADDED] Ability to express the max_file_store and max_memory_store as a string (100M, etc..)
Resolves #2754

### Changes proposed in this pull request:

 - Adds a function to check if max_file_store is a string or int64. Possibly could be reused again.

/cc @nats-io/core


Patch:
diff --git a/server/opts.go b/server/opts.go
index 044ce294..2881545a 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -1680,6 +1680,40 @@ func parseJetStreamForAccount(v interface{}, acc *Account, errors *[]error, warn
 	return nil
 }
 
+// takes in a storage size as either an int or a string and returns an int64 value based on the input.
+func getStorageSize(v interface{}) (int64, error) {
+	_, ok := v.(int64)
+	if ok {
+		return v.(int64), nil
+	}
+
+	s, ok := v.(string)
+	if !ok {
+		return 0, fmt.Errorf("must be int64 or string")
+	}
+
+	if s == "" {
+		return 0, nil
+	}
+
+	suffix := s[len(s)-1:]
+	prefix := s[:len(s)-1]
+	num, err := strconv.ParseInt(prefix, 10, 64)
+	if err != nil {
+		return 0, err
+	}
+
+	suffixMap := map[string]int64{"K": 10, "M": 20, "G": 30, "T": 40}
+
+	mult, ok := suffixMap[suffix]
+	if !ok {
+		return 0, fmt.Errorf("sizes defined as strings must end in K, M, G, T")
+	}
+	num *= 1 << mult
+
+	return num, nil
+}
+
 // Parse enablement of jetstream for a server.
 func parseJetStream(v interface{}, opts *Options, errors *[]error, warnings *[]error) error {
 	var lt token
@@ -1711,10 +1745,18 @@ func parseJetStream(v interface{}, opts *Options, errors *[]error, warnings *[]e
 				}
 				opts.StoreDir = mv.(string)
 			case "max_memory_store", "max_mem_store", "max_mem":
-				opts.JetStreamMaxMemory = mv.(int64)
+				s, err := getStorageSize(mv)
+				if err != nil {
+					return &configErr{tk, fmt.Sprintf("max_mem_store %s", err)}
+				}
+				opts.JetStreamMaxMemory = s
 				opts.maxMemSet = true
 			case "max_file_store", "max_file":
-				opts.JetStreamMaxStore = mv.(int64)
+				s, err := getStorageSize(mv)
+				if err != nil {
+					return &configErr{tk, fmt.Sprintf("max_file_store %s", err)}
+				}
+				opts.JetStreamMaxStore = s
 				opts.maxStoreSet = true
 			case "domain":
 				opts.JetStreamDomain = mv.(string)


Test patch:
diff --git a/server/opts_test.go b/server/opts_test.go
index 1e73f167..8a579d1f 100644
--- a/server/opts_test.go
+++ b/server/opts_test.go
@@ -3256,3 +3256,32 @@ func TestMaxSubTokens(t *testing.T) {
 		t.Fatal("Did not get the permissions error")
 	}
 }
+
+func TestGetStorageSize(t *testing.T) {
+	tt := []struct {
+		input string
+		want  int64
+		err   bool
+	}{
+		{"1K", 1024, false},
+		{"1M", 1048576, false},
+		{"1G", 1073741824, false},
+		{"1T", 1099511627776, false},
+		{"1L", 0, true},
+		{"TT", 0, true},
+		{"", 0, false},
+	}
+
+	for _, v := range tt {
+		var testErr bool
+		got, err := getStorageSize(v.input)
+		if err != nil {
+			testErr = true
+		}
+
+		if got != v.want || v.err != testErr {
+			t.Errorf("Got: %v, want %v with error: %v", got, v.want, testErr)
+		}
+	}
+
+}