mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
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:
parent
95f81372e2
commit
b95d45e803
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user