Skip to content

Commit

Permalink
Define dynamic config and integrate in service bootstrap (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
madhuravi authored Feb 23, 2018
1 parent 095fbd7 commit 0182700
Show file tree
Hide file tree
Showing 25 changed files with 493 additions and 78 deletions.
8 changes: 4 additions & 4 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ func (s *server) startService() common.Daemon {

switch s.name {
case frontendService:
daemon = frontend.NewService(&params, frontend.NewConfig())
daemon = frontend.NewService(&params)
case historyService:
daemon = history.NewService(&params, history.NewConfig(s.cfg.Cassandra.NumHistoryShards))
daemon = history.NewService(&params)
case matchingService:
daemon = matching.NewService(&params, matching.NewConfig())
daemon = matching.NewService(&params)
case workerService:
daemon = worker.NewService(&params, worker.NewConfig())
daemon = worker.NewService(&params)
}

go execute(daemon, s.doneC)
Expand Down
131 changes: 131 additions & 0 deletions common/service/dynamicconfig/config.go
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)
}
}
37 changes: 37 additions & 0 deletions common/service/dynamicconfig/config_benchmark_test.go
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())
}
}
96 changes: 96 additions & 0 deletions common/service/dynamicconfig/config_test.go
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")
}
83 changes: 83 additions & 0 deletions common/service/dynamicconfig/constants.go
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
)
Loading

0 comments on commit 0182700

Please sign in to comment.