memory manager: add new flag type BracketSeparatedSliceMapStringString

Add BracketSeparatedSliceMapStringString to parse config like the below
{numa-node=0,type=memory,limit=1Gi},{numa-node=1,type=memory,limit=1Gi}

Signed-off-by: Byonggon Chun <bg.chun@samsung.com>
This commit is contained in:
Artyom Lukianov 2020-10-08 18:23:52 +03:00
parent 95f81372e2
commit b95d45e803
2 changed files with 296 additions and 0 deletions

View File

@ -0,0 +1,118 @@
/*
Copyright 2020 The Kubernetes Authors.
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 flag
import (
"fmt"
"sort"
"strings"
)
// BracketSeparatedSliceMapStringString can be set from the command line with the format `--flag {key=value, ...}, {...}`.
// Multiple comma-separated key-value pairs in a braket(`{}`) in a single invocation are supported. For example: `--flag {key=value, key=value, ...}`.
// Multiple braket-separated list of key-value pairs in a single invocation are supported. For example: `--flag {key=value, key=value}, {key=value, key=value}`.
type BracketSeparatedSliceMapStringString struct {
Value *[]map[string]string
initialized bool // set to true after the first Set call
}
// NewBracketSeparatedSliceMapStringString takes a pointer to a []map[string]string and returns the
// BracketSeparatedSliceMapStringString flag parsing shim for that map
func NewBracketSeparatedSliceMapStringString(m *[]map[string]string) *BracketSeparatedSliceMapStringString {
return &BracketSeparatedSliceMapStringString{Value: m}
}
// Set implements github.com/spf13/pflag.Value
func (m *BracketSeparatedSliceMapStringString) Set(value string) error {
if m.Value == nil {
return fmt.Errorf("no target (nil pointer to []map[string]string)")
}
if !m.initialized || *m.Value == nil {
*m.Value = make([]map[string]string, 0)
m.initialized = true
}
value = strings.TrimSpace(value)
// split here
//{numa-node=0,memory-type=memory,limit=1Gi},{numa-node=1,memory-type=memory,limit=1Gi},{numa-node=1,memory-type=memory,limit=1Gi}
// for _, split := range strings.Split(value, "{") {
// split = strings.TrimRight(split, ",")
// split = strings.TrimRight(split, "}")
for _, split := range strings.Split(value, ",{") {
//split = strings.TrimRight(split, ",")
split = strings.TrimLeft(split, "{")
split = strings.TrimRight(split, "}")
if len(split) == 0 {
continue
}
// now we have "numa-node=1,memory-type=memory,limit=1Gi"
tmpRawMap := make(map[string]string)
tmpMap:= NewMapStringString(&tmpRawMap)
if err := tmpMap.Set(split); err != nil {
return fmt.Errorf("could not parse String: (%s): %v", value, err)
}
*m.Value = append(*m.Value, tmpRawMap)
}
return nil
}
// String implements github.com/spf13/pflag.Value
func (m *BracketSeparatedSliceMapStringString) String() string {
if m == nil || m.Value == nil {
return ""
}
var slices []string
for _, configMap := range *m.Value {
var tmpPairs []string
var keys []string
for key := range configMap {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
tmpPairs = append(tmpPairs, fmt.Sprintf("%s=%s", key, configMap[key]))
}
if len(tmpPairs) != 0 {
slices = append(slices, "{" + strings.Join(tmpPairs, ",") + "}")
}
}
sort.Strings(slices)
return strings.Join(slices, ",")
}
// Type implements github.com/spf13/pflag.Value
func (*BracketSeparatedSliceMapStringString) Type() string {
return "BracketSeparatedSliceMapStringString"
}
// Empty implements OmitEmpty
func (m *BracketSeparatedSliceMapStringString) Empty() bool {
return !m.initialized || m.Value == nil || len(*m.Value) == 0
}

View File

@ -0,0 +1,178 @@
/*
Copyright 2020 The Kubernetes Authors.
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 flag
import (
"reflect"
"testing"
)
func TestStringBracketSeparatedSliceMapStringString(t *testing.T) {
var nilSliceMap []map[string]string
testCases := []struct {
desc string
m *BracketSeparatedSliceMapStringString
expect string
}{
{"nill", NewBracketSeparatedSliceMapStringString(&nilSliceMap), ""},
{"empty", NewBracketSeparatedSliceMapStringString(&[]map[string]string{}), ""},
{"one key", NewBracketSeparatedSliceMapStringString(&[]map[string]string{{"a": "string"}}), "{a=string}"},
{"two keys", NewBracketSeparatedSliceMapStringString(&[]map[string]string{{"a": "string", "b": "string"}}), "{a=string,b=string}"},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
str := tc.m.String()
if tc.expect != str {
t.Fatalf("expect %q but got %q", tc.expect, str)
}
})
}
}
func TestSetBracketSeparatedSliceMapStringString(t *testing.T) {
var nilMap []map[string]string
testCases := []struct {
desc string
vals []string
start *BracketSeparatedSliceMapStringString
expect *BracketSeparatedSliceMapStringString
err string
}{
// we initialize the map with a default key that should be cleared by Set
{"clears defaults", []string{""},
NewBracketSeparatedSliceMapStringString(&[]map[string]string{{"default": ""}}),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{},
}, ""},
// make sure we still allocate for "initialized" multimaps where Multimap was initially set to a nil map
{"allocates map if currently nil", []string{""},
&BracketSeparatedSliceMapStringString{initialized: true, Value: &nilMap},
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{},
}, ""},
// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
{"empty", []string{""},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{},
}, ""},
{"empty braket", []string{"{}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{},
}, ""},
{"missing braket", []string{"a=string, b=string"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string", "b": "string"}},
}, ""},
{"empty key", []string{"{=string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"": "string"}},
}, ""},
{"one key", []string{"{a=string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string"}},
}, ""},
{"two keys", []string{"{a=string,b=string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string", "b": "string"}},
}, ""},
{"two duplecated keys", []string{"{a=string,a=string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string"}},
}, ""},
{"two keys with space", []string{"{a = string, b = string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string", "b": "string"}},
}, ""},
{"two keys, multiple Set invocations", []string{"{a=string, b=string}", "{a=string, b=string}"},
NewBracketSeparatedSliceMapStringString(&nilMap),
&BracketSeparatedSliceMapStringString{
initialized: true,
Value: &[]map[string]string{{"a": "string", "b": "string"}, {"a": "string", "b": "string"}},
}, ""},
{"no target", []string{""},
NewBracketSeparatedSliceMapStringString(nil),
nil,
"no target (nil pointer to []map[string]string)"},
}
for _, tc := range testCases {
nilMap = nil
t.Run(tc.desc, func(t *testing.T) {
var err error
for _, val := range tc.vals {
err = tc.start.Set(val)
if err != nil {
break
}
}
if tc.err != "" {
if err == nil || err.Error() != tc.err {
t.Fatalf("expect error %s but got %v", tc.err, err)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(tc.expect, tc.start) {
t.Fatalf("expect %#v but got %#v", tc.expect, tc.start)
}
})
}
}
func TestEmptyBracketSeparatedSliceMapStringString(t *testing.T) {
var nilSliceMap []map[string]string
notEmpty := &BracketSeparatedSliceMapStringString{
Value: &[]map[string]string{{"a": "int", "b": "string", "c": "string"}},
initialized: true,
}
testCases := []struct {
desc string
m *BracketSeparatedSliceMapStringString
expect bool
}{
{"nil", NewBracketSeparatedSliceMapStringString(&nilSliceMap), true},
{"empty", NewBracketSeparatedSliceMapStringString(&[]map[string]string{}), true},
{"populated", notEmpty, false},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
ret := tc.m.Empty()
if ret != tc.expect {
t.Fatalf("expect %t but got %t", tc.expect, ret)
}
})
}
}