-
Notifications
You must be signed in to change notification settings - Fork 805
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Define dynamic config and integrate in service bootstrap (#543)
- Loading branch information
Showing
25 changed files
with
493 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package dynamicconfig | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
) | ||
|
||
// Client allows fetching values from a dynamic configuration system NOTE: This does not have async | ||
// options right now. In the interest of keeping it minimal, we can add when requirement arises. | ||
type Client interface { | ||
GetValue(name Key) (interface{}, error) | ||
GetValueWithFilters(name Key, filters map[Filter]interface{}) (interface{}, error) | ||
} | ||
|
||
type nopClient struct{} | ||
|
||
func (mc *nopClient) GetValue(name Key) (interface{}, error) { | ||
return nil, errors.New("unable to find key") | ||
} | ||
|
||
func (mc *nopClient) GetValueWithFilters( | ||
name Key, filters map[Filter]interface{}, | ||
) (interface{}, error) { | ||
return nil, errors.New("unable to find key") | ||
} | ||
|
||
// NewNopCollection creates a new nop collection | ||
func NewNopCollection() *Collection { | ||
return NewCollection(&nopClient{}) | ||
} | ||
|
||
// NewCollection creates a new collection | ||
func NewCollection(client Client) *Collection { | ||
return &Collection{client} | ||
} | ||
|
||
// Collection wraps dynamic config client with a closure so that across the code, the config values | ||
// can be directly accessed by calling the function without propagating the client everywhere in | ||
// code | ||
type Collection struct { | ||
client Client | ||
} | ||
|
||
// GetIntPropertyWithTaskList gets property with taskList filter and asserts that it's an integer | ||
func (c *Collection) GetIntPropertyWithTaskList(key Key, defaultVal int) func(string) int { | ||
return func(taskList string) int { | ||
return c.getPropertyWithStringFilter(key, defaultVal, TaskListName)(taskList).(int) | ||
} | ||
} | ||
|
||
// GetDurationPropertyWithTaskList gets property with taskList filter and asserts that it's time.Duration | ||
func (c *Collection) GetDurationPropertyWithTaskList( | ||
key Key, defaultVal time.Duration, | ||
) func(string) time.Duration { | ||
return func(taskList string) time.Duration { | ||
return c.getPropertyWithStringFilter(key, defaultVal, TaskListName)(taskList).(time.Duration) | ||
} | ||
} | ||
|
||
func (c *Collection) getPropertyWithStringFilter( | ||
key Key, defaultVal interface{}, filter Filter, | ||
) func(string) interface{} { | ||
return func(filterVal string) interface{} { | ||
filters := make(map[Filter]interface{}) | ||
filters[filter] = filterVal | ||
val, err := c.client.GetValueWithFilters(key, filters) | ||
if err != nil { | ||
return defaultVal | ||
} | ||
return val | ||
} | ||
} | ||
|
||
// GetProperty gets a eface property and returns defaultVal if property is not found | ||
func (c *Collection) GetProperty(key Key, defaultVal interface{}) func() interface{} { | ||
return func() interface{} { | ||
val, err := c.client.GetValue(key) | ||
if err != nil { | ||
return defaultVal | ||
} | ||
return val | ||
} | ||
} | ||
|
||
// GetIntProperty gets property and asserts that it's an integer | ||
func (c *Collection) GetIntProperty(key Key, defaultVal int) func() int { | ||
return func() int { | ||
return c.GetProperty(key, defaultVal)().(int) | ||
} | ||
} | ||
|
||
// GetFloat64Property gets property and asserts that it's a float64 | ||
func (c *Collection) GetFloat64Property(key Key, defaultVal float64) func() float64 { | ||
return func() float64 { | ||
return c.GetProperty(key, defaultVal)().(float64) | ||
} | ||
} | ||
|
||
// GetDurationProperty gets property and asserts that it's a duration | ||
func (c *Collection) GetDurationProperty(key Key, defaultVal time.Duration) func() time.Duration { | ||
return func() time.Duration { | ||
return c.GetProperty(key, defaultVal)().(time.Duration) | ||
} | ||
} | ||
|
||
// GetBoolProperty gets property and asserts that it's an bool | ||
func (c *Collection) GetBoolProperty(key Key, defaultVal bool) func() bool { | ||
return func() bool { | ||
return c.GetProperty(key, defaultVal)().(bool) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package dynamicconfig | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func BenchmarkGetIntProperty(b *testing.B) { | ||
client := newInMemoryClient() | ||
cln := NewCollection(client) | ||
key := MaxTaskBatchSize | ||
for i := 0; i < b.N; i++ { | ||
size := cln.GetIntProperty(key, 10) | ||
assert.Equal(b, 10, size()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package dynamicconfig | ||
|
||
import ( | ||
"errors" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type inMemoryClient struct { | ||
values map[Key]interface{} | ||
sync.RWMutex | ||
} | ||
|
||
func newInMemoryClient() *inMemoryClient { | ||
return &inMemoryClient{values: make(map[Key]interface{})} | ||
} | ||
|
||
func (mc *inMemoryClient) SetValue(key Key, value interface{}) { | ||
mc.Lock() | ||
defer mc.Unlock() | ||
mc.values[key] = value | ||
} | ||
|
||
func (mc *inMemoryClient) GetValue(key Key) (interface{}, error) { | ||
mc.RLock() | ||
defer mc.RUnlock() | ||
if val, ok := mc.values[key]; ok { | ||
return val, nil | ||
} | ||
return nil, errors.New("unable to find key") | ||
} | ||
|
||
func (mc *inMemoryClient) GetValueWithFilters( | ||
name Key, filters map[Filter]interface{}, | ||
) (interface{}, error) { | ||
return mc.GetValue(name) | ||
} | ||
|
||
type configSuite struct { | ||
suite.Suite | ||
client *inMemoryClient | ||
cln *Collection | ||
} | ||
|
||
func TestConfigSuite(t *testing.T) { | ||
s := new(configSuite) | ||
suite.Run(t, s) | ||
} | ||
|
||
func (s *configSuite) SetupSuite() { | ||
s.client = newInMemoryClient() | ||
s.cln = NewCollection(s.client) | ||
} | ||
|
||
func (s *configSuite) TestGetIntProperty() { | ||
key := MaxTaskBatchSize | ||
size := s.cln.GetIntProperty(key, 10) | ||
s.Equal(10, size()) | ||
s.client.SetValue(key, 50) | ||
s.Equal(50, size()) | ||
s.client.SetValue(key, "hello world") | ||
s.Panics(func() { size() }, "Should panic") | ||
} | ||
|
||
func (s *configSuite) TestGetDurationProperty() { | ||
key := MatchingLongPollExpirationInterval | ||
interval := s.cln.GetDurationProperty(key, time.Second) | ||
s.Equal(time.Second, interval()) | ||
s.client.SetValue(key, time.Minute) | ||
s.Equal(time.Minute, interval()) | ||
s.client.SetValue(key, 10) | ||
s.Panics(func() { interval() }, "Should panic") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package dynamicconfig | ||
|
||
// Key represents a key/property stored in dynamic config | ||
type Key int | ||
|
||
func (k Key) String() string { | ||
if k <= unknownKey || int(k) >= len(keys) { | ||
return keys[unknownKey] | ||
} | ||
return keys[k] | ||
} | ||
|
||
const ( | ||
_matchingRoot = "matching." | ||
_matchingTaskListRoot = _matchingRoot + "taskList." | ||
_historyRoot = "history." | ||
) | ||
|
||
var keys = []string{ | ||
"unknownKey", | ||
_matchingTaskListRoot + "MinTaskThrottlingBurstSize", | ||
_matchingTaskListRoot + "MaxTaskBatchSize", | ||
_matchingTaskListRoot + "LongPollExpirationInterval", | ||
_historyRoot + "LongPollExpirationInterval", | ||
} | ||
|
||
const ( | ||
// The order of constants is important. It should match the order in the keys array above. | ||
unknownKey Key = iota | ||
// Matching keys | ||
|
||
// MinTaskThrottlingBurstSize is the minimum burst size for task list throttling | ||
MinTaskThrottlingBurstSize | ||
// MaxTaskBatchSize is the maximum batch size to fetch from the task buffer | ||
MaxTaskBatchSize | ||
// MatchingLongPollExpirationInterval is the long poll expiration interval in the matching service | ||
MatchingLongPollExpirationInterval | ||
// HistoryLongPollExpirationInterval is the long poll expiration interval in the history service | ||
HistoryLongPollExpirationInterval | ||
) | ||
|
||
// Filter represents a filter on the dynamic config key | ||
type Filter int | ||
|
||
func (k Filter) String() string { | ||
keys := []string{ | ||
"unknownFilter", | ||
"domainName", | ||
"taskListName", | ||
} | ||
if k <= unknownFilter || k > TaskListName { | ||
return keys[unknownFilter] | ||
} | ||
return keys[k] | ||
} | ||
|
||
const ( | ||
unknownFilter Filter = iota | ||
// DomainName is the domain name | ||
DomainName | ||
// TaskListName is the tasklist name | ||
TaskListName | ||
) |
Oops, something went wrong.