go.temporal.io/server@v1.23.0/common/dynamicconfig/collection_test.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package dynamicconfig
    26  
    27  import (
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/stretchr/testify/suite"
    32  
    33  	enumsspb "go.temporal.io/server/api/enums/v1"
    34  	"go.temporal.io/server/common/log"
    35  )
    36  
    37  const (
    38  	// dynamic config for tests
    39  	unknownKey                                        = "unknownKey"
    40  	testGetPropertyKey                                = "testGetPropertyKey"
    41  	testCaseInsensitivePropertyKey                    = "testCaseInsensitivePropertyKey"
    42  	testGetIntPropertyKey                             = "testGetIntPropertyKey"
    43  	testGetFloat64PropertyKey                         = "testGetFloat64PropertyKey"
    44  	testGetDurationPropertyKey                        = "testGetDurationPropertyKey"
    45  	testGetBoolPropertyKey                            = "testGetBoolPropertyKey"
    46  	testGetStringPropertyKey                          = "testGetStringPropertyKey"
    47  	testGetMapPropertyKey                             = "testGetMapPropertyKey"
    48  	testGetIntPropertyFilteredByNamespaceKey          = "testGetIntPropertyFilteredByNamespaceKey"
    49  	testGetDurationPropertyFilteredByNamespaceKey     = "testGetDurationPropertyFilteredByNamespaceKey"
    50  	testGetIntPropertyFilteredByTaskQueueInfoKey      = "testGetIntPropertyFilteredByTaskQueueInfoKey"
    51  	testGetDurationPropertyFilteredByTaskQueueInfoKey = "testGetDurationPropertyFilteredByTaskQueueInfoKey"
    52  	testGetDurationPropertyFilteredByTaskTypeKey      = "testGetDurationPropertyFilteredByTaskTypeKey"
    53  	testGetDurationPropertyStructuredDefaults         = "testGetDurationPropertyStructuredDefaults"
    54  	testGetBoolPropertyFilteredByNamespaceIDKey       = "testGetBoolPropertyFilteredByNamespaceIDKey"
    55  	testGetBoolPropertyFilteredByTaskQueueInfoKey     = "testGetBoolPropertyFilteredByTaskQueueInfoKey"
    56  	testGetStringPropertyFilteredByNamespaceIDKey     = "testGetStringPropertyFilteredByNamespaceIDKey"
    57  )
    58  
    59  // Note: fileBasedClientSuite also heavily tests Collection, since some tests are easier with data
    60  // provided from a file.
    61  type collectionSuite struct {
    62  	suite.Suite
    63  	client StaticClient
    64  	cln    *Collection
    65  }
    66  
    67  func TestCollectionSuite(t *testing.T) {
    68  	s := new(collectionSuite)
    69  	suite.Run(t, s)
    70  }
    71  
    72  func (s *collectionSuite) SetupSuite() {
    73  	s.client = make(StaticClient)
    74  	logger := log.NewNoopLogger()
    75  	s.cln = NewCollection(s.client, logger)
    76  }
    77  
    78  func (s *collectionSuite) TestGetIntProperty() {
    79  	value := s.cln.GetIntProperty(testGetIntPropertyKey, 10)
    80  	s.Equal(10, value())
    81  	s.client[testGetIntPropertyKey] = 50
    82  	s.Equal(50, value())
    83  }
    84  
    85  func (s *collectionSuite) TestGetIntPropertyFilteredByNamespace() {
    86  	namespace := "testNamespace"
    87  	value := s.cln.GetIntPropertyFilteredByNamespace(testGetIntPropertyFilteredByNamespaceKey, 10)
    88  	s.Equal(10, value(namespace))
    89  	s.client[testGetIntPropertyFilteredByNamespaceKey] = 50
    90  	s.Equal(50, value(namespace))
    91  }
    92  
    93  func (s *collectionSuite) TestGetStringPropertyFnWithNamespaceFilter() {
    94  	namespace := "testNamespace"
    95  	value := s.cln.GetStringPropertyFnWithNamespaceFilter(DefaultEventEncoding, "abc")
    96  	s.Equal("abc", value(namespace))
    97  	s.client[DefaultEventEncoding] = "efg"
    98  	s.Equal("efg", value(namespace))
    99  }
   100  
   101  func (s *collectionSuite) TestGetStringPropertyFnWithNamespaceIDFilter() {
   102  	namespaceID := "testNamespaceID"
   103  	value := s.cln.GetStringPropertyFnWithNamespaceIDFilter(testGetStringPropertyFilteredByNamespaceIDKey, "abc")
   104  	s.Equal("abc", value(namespaceID))
   105  	s.client[testGetStringPropertyFilteredByNamespaceIDKey] = "efg"
   106  	s.Equal("efg", value(namespaceID))
   107  }
   108  
   109  func (s *collectionSuite) TestGetIntPropertyFilteredByTaskQueueInfo() {
   110  	namespace := "testNamespace"
   111  	taskQueue := "testTaskQueue"
   112  	value := s.cln.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyFilteredByTaskQueueInfoKey, 10)
   113  	s.Equal(10, value(namespace, taskQueue, 0))
   114  	s.client[testGetIntPropertyFilteredByTaskQueueInfoKey] = 50
   115  	s.Equal(50, value(namespace, taskQueue, 0))
   116  }
   117  
   118  func (s *collectionSuite) TestGetFloat64Property() {
   119  	value := s.cln.GetFloat64Property(testGetFloat64PropertyKey, 0.1)
   120  	s.Equal(0.1, value())
   121  	s.client[testGetFloat64PropertyKey] = 0.01
   122  	s.Equal(0.01, value())
   123  }
   124  
   125  func (s *collectionSuite) TestGetBoolProperty() {
   126  	value := s.cln.GetBoolProperty(testGetBoolPropertyKey, true)
   127  	s.Equal(true, value())
   128  	s.client[testGetBoolPropertyKey] = false
   129  	s.Equal(false, value())
   130  }
   131  
   132  func (s *collectionSuite) TestGetBoolPropertyFilteredByNamespaceID() {
   133  	namespaceID := "testNamespaceID"
   134  	value := s.cln.GetBoolPropertyFnWithNamespaceIDFilter(testGetBoolPropertyFilteredByNamespaceIDKey, true)
   135  	s.Equal(true, value(namespaceID))
   136  	s.client[testGetBoolPropertyFilteredByNamespaceIDKey] = false
   137  	s.Equal(false, value(namespaceID))
   138  }
   139  
   140  func (s *collectionSuite) TestGetBoolPropertyFilteredByTaskQueueInfo() {
   141  	namespace := "testNamespace"
   142  	taskQueue := "testTaskQueue"
   143  	value := s.cln.GetBoolPropertyFilteredByTaskQueueInfo(testGetBoolPropertyFilteredByTaskQueueInfoKey, false)
   144  	s.Equal(false, value(namespace, taskQueue, 0))
   145  	s.client[testGetBoolPropertyFilteredByTaskQueueInfoKey] = true
   146  	s.Equal(true, value(namespace, taskQueue, 0))
   147  }
   148  
   149  func (s *collectionSuite) TestGetDurationProperty() {
   150  	value := s.cln.GetDurationProperty(testGetDurationPropertyKey, time.Second)
   151  	s.Equal(time.Second, value())
   152  	s.client[testGetDurationPropertyKey] = time.Minute
   153  	s.Equal(time.Minute, value())
   154  	s.client[testGetDurationPropertyKey] = 33
   155  	s.Equal(33*time.Second, value())
   156  	s.client[testGetDurationPropertyKey] = "33"
   157  	s.Equal(33*time.Second, value())
   158  }
   159  
   160  func (s *collectionSuite) TestGetDurationPropertyFilteredByNamespace() {
   161  	namespace := "testNamespace"
   162  	value := s.cln.GetDurationPropertyFilteredByNamespace(testGetDurationPropertyFilteredByNamespaceKey, time.Second)
   163  	s.Equal(time.Second, value(namespace))
   164  	s.client[testGetDurationPropertyFilteredByNamespaceKey] = time.Minute
   165  	s.Equal(time.Minute, value(namespace))
   166  }
   167  
   168  func (s *collectionSuite) TestGetDurationPropertyFilteredByTaskQueueInfo() {
   169  	namespace := "testNamespace"
   170  	taskQueue := "testTaskQueue"
   171  	value := s.cln.GetDurationPropertyFilteredByTaskQueueInfo(testGetDurationPropertyFilteredByTaskQueueInfoKey, time.Second)
   172  	s.Equal(time.Second, value(namespace, taskQueue, 0))
   173  	s.client[testGetDurationPropertyFilteredByTaskQueueInfoKey] = time.Minute
   174  	s.Equal(time.Minute, value(namespace, taskQueue, 0))
   175  }
   176  
   177  func (s *collectionSuite) TestGetDurationPropertyFilteredByTaskType() {
   178  	taskType := enumsspb.TASK_TYPE_UNSPECIFIED
   179  	value := s.cln.GetDurationPropertyFilteredByTaskType(testGetDurationPropertyFilteredByTaskTypeKey, time.Second)
   180  	s.Equal(time.Second, value(taskType))
   181  	s.client[testGetDurationPropertyFilteredByTaskTypeKey] = time.Minute
   182  	s.Equal(time.Minute, value(taskType))
   183  }
   184  
   185  func (s *collectionSuite) TestGetDurationPropertyStructuredDefaults() {
   186  	defaults := []ConstrainedValue{
   187  		{
   188  			Constraints: Constraints{
   189  				Namespace:     "ns2",
   190  				TaskQueueName: "tq2",
   191  			},
   192  			Value: 2 * time.Minute,
   193  		},
   194  		{
   195  			Constraints: Constraints{
   196  				TaskQueueName: "tq2",
   197  			},
   198  			Value: 5 * time.Minute,
   199  		},
   200  		{
   201  			Value: 7 * time.Minute,
   202  		},
   203  	}
   204  	value := s.cln.GetDurationPropertyFilteredByTaskQueueInfo(testGetDurationPropertyStructuredDefaults, defaults)
   205  	s.Equal(7*time.Minute, value("ns1", "tq1", 0))
   206  	s.Equal(7*time.Minute, value("ns2", "tq1", 0))
   207  	s.Equal(5*time.Minute, value("ns1", "tq2", 0))
   208  	s.Equal(2*time.Minute, value("ns2", "tq2", 0))
   209  
   210  	// user-set values should take precedence. defaults are included below in the interleaved
   211  	// precedence order to make the test easier to read
   212  	s.client[testGetDurationPropertyStructuredDefaults] = []ConstrainedValue{
   213  		{
   214  			Constraints: Constraints{
   215  				Namespace:     "ns2",
   216  				TaskQueueName: "tq2",
   217  			},
   218  			Value: 2 * time.Second,
   219  		},
   220  		// {
   221  		//   Constraints: Constraints{
   222  		//     Namespace:     "ns2",
   223  		//     TaskQueueName: "tq2",
   224  		//   },
   225  		//   Value: 2 * time.Minute,
   226  		// },
   227  		// {
   228  		//   Constraints: Constraints{
   229  		//     TaskQueueName: "tq2",
   230  		//   },
   231  		//   Value: 5 * time.Minute,
   232  		// },
   233  		{
   234  			Constraints: Constraints{
   235  				Namespace: "ns1",
   236  			},
   237  			Value: 5 * time.Second,
   238  		},
   239  		{
   240  			Value: 7 * time.Second,
   241  		},
   242  		// {
   243  		//   Value: 7 * time.Minute,
   244  		// },
   245  	}
   246  
   247  	s.Equal(5*time.Second, value("ns1", "tq1", 0))
   248  	s.Equal(7*time.Second, value("ns2", "tq1", 0))
   249  	s.Equal(5*time.Minute, value("ns1", "tq2", 0))
   250  	s.Equal(2*time.Second, value("ns2", "tq2", 0))
   251  }
   252  
   253  func (s *collectionSuite) TestGetMapProperty() {
   254  	val := map[string]interface{}{
   255  		"testKey": 123,
   256  	}
   257  	value := s.cln.GetMapProperty(testGetMapPropertyKey, val)
   258  	s.Equal(val, value())
   259  	val["testKey"] = "321"
   260  	s.client[testGetMapPropertyKey] = val
   261  	s.Equal(val, value())
   262  	s.Equal("321", value()["testKey"])
   263  }
   264  
   265  func (s *collectionSuite) TestFindMatch() {
   266  	testCases := []struct {
   267  		v       []ConstrainedValue
   268  		filters []Constraints
   269  		matched bool
   270  	}{
   271  		{
   272  			v: []ConstrainedValue{
   273  				{Constraints: Constraints{}},
   274  			},
   275  			filters: []Constraints{
   276  				{Namespace: "some random namespace"},
   277  			},
   278  			matched: false,
   279  		},
   280  		{
   281  			v: []ConstrainedValue{
   282  				{Constraints: Constraints{Namespace: "samples-namespace"}},
   283  			},
   284  			filters: []Constraints{
   285  				{Namespace: "some random namespace"},
   286  			},
   287  			matched: false,
   288  		},
   289  		{
   290  			v: []ConstrainedValue{
   291  				{Constraints: Constraints{Namespace: "samples-namespace", TaskQueueName: "sample-task-queue"}},
   292  			},
   293  			filters: []Constraints{
   294  				{Namespace: "samples-namespace", TaskQueueName: "sample-task-queue"},
   295  			},
   296  			matched: true,
   297  		},
   298  		{
   299  			v: []ConstrainedValue{
   300  				{Constraints: Constraints{Namespace: "samples-namespace"}},
   301  			},
   302  			filters: []Constraints{
   303  				{TaskQueueName: "sample-task-queue"},
   304  			},
   305  			matched: false,
   306  		},
   307  	}
   308  
   309  	for _, tc := range testCases {
   310  		_, err := findMatch(tc.v, nil, tc.filters)
   311  		s.Equal(tc.matched, err == nil)
   312  		_, err = findMatch(nil, tc.v, tc.filters)
   313  		s.Equal(tc.matched, err == nil)
   314  	}
   315  }
   316  
   317  func BenchmarkCollection(b *testing.B) {
   318  	// client with just one value
   319  	client1 := StaticClient(map[Key]any{
   320  		MatchingMaxTaskBatchSize: []ConstrainedValue{{Value: 12}},
   321  	})
   322  	cln1 := NewCollection(client1, log.NewNoopLogger())
   323  	b.Run("global int", func(b *testing.B) {
   324  		b.ReportAllocs()
   325  		for i := 0; i < b.N/2; i++ {
   326  			size := cln1.GetIntProperty(MatchingMaxTaskBatchSize, 10)
   327  			_ = size()
   328  			size = cln1.GetIntProperty(MatchingGetTasksBatchSize, 10)
   329  			_ = size()
   330  		}
   331  	})
   332  	b.Run("namespace int", func(b *testing.B) {
   333  		b.ReportAllocs()
   334  		for i := 0; i < b.N/2; i++ {
   335  			size := cln1.GetIntPropertyFilteredByNamespace(MatchingMaxTaskBatchSize, 10)
   336  			_ = size("my-namespace")
   337  			size = cln1.GetIntPropertyFilteredByNamespace(MatchingGetTasksBatchSize, 10)
   338  			_ = size("my-namespace")
   339  		}
   340  	})
   341  	b.Run("taskqueue int", func(b *testing.B) {
   342  		b.ReportAllocs()
   343  		for i := 0; i < b.N/2; i++ {
   344  			size := cln1.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, 10)
   345  			_ = size("my-namespace", "my-task-queue", 1)
   346  			size = cln1.GetIntPropertyFilteredByTaskQueueInfo(MatchingGetTasksBatchSize, 10)
   347  			_ = size("my-namespace", "my-task-queue", 1)
   348  		}
   349  	})
   350  
   351  	// client with more constrained values
   352  	client2 := StaticClient(map[Key]any{
   353  		MatchingMaxTaskBatchSize: []ConstrainedValue{
   354  			{
   355  				Constraints: Constraints{
   356  					TaskQueueName: "other-tq",
   357  				},
   358  				Value: 18,
   359  			},
   360  			{
   361  				Constraints: Constraints{
   362  					Namespace: "other-ns",
   363  				},
   364  				Value: 15,
   365  			},
   366  		},
   367  	})
   368  	cln2 := NewCollection(client2, log.NewNoopLogger())
   369  	b.Run("single default", func(b *testing.B) {
   370  		b.ReportAllocs()
   371  		for i := 0; i < b.N/4; i++ {
   372  			size := cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, 10)
   373  			_ = size("my-namespace", "my-task-queue", 1)
   374  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, 10)
   375  			_ = size("my-namespace", "other-tq", 1)
   376  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, 10)
   377  			_ = size("other-ns", "my-task-queue", 1)
   378  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, 10)
   379  			_ = size("other-ns", "other-tq", 1)
   380  		}
   381  	})
   382  	b.Run("structured default", func(b *testing.B) {
   383  		b.ReportAllocs()
   384  		for i := 0; i < b.N/4; i++ {
   385  			size := cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, defaultNumTaskQueuePartitions)
   386  			_ = size("my-namespace", "my-task-queue", 1)
   387  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, defaultNumTaskQueuePartitions)
   388  			_ = size("my-namespace", "other-tq", 1)
   389  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, defaultNumTaskQueuePartitions)
   390  			_ = size("other-ns", "my-task-queue", 1)
   391  			size = cln2.GetIntPropertyFilteredByTaskQueueInfo(MatchingMaxTaskBatchSize, defaultNumTaskQueuePartitions)
   392  			_ = size("other-ns", "other-tq", 1)
   393  		}
   394  	})
   395  }