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 }