github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/dynamic_cluster_test.go (about) 1 // Copyright (c) 2020 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package m3 22 23 import ( 24 "errors" 25 "sort" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/client" 30 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 31 "github.com/m3db/m3/src/dbnode/namespace" 32 "github.com/m3db/m3/src/dbnode/retention" 33 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 34 xclock "github.com/m3db/m3/src/x/clock" 35 "github.com/m3db/m3/src/x/ident" 36 "github.com/m3db/m3/src/x/instrument" 37 xtest "github.com/m3db/m3/src/x/test" 38 xwatch "github.com/m3db/m3/src/x/watch" 39 40 "github.com/golang/mock/gomock" 41 "github.com/stretchr/testify/assert" 42 "github.com/stretchr/testify/require" 43 ) 44 45 var ( 46 defaultTestNs1ID = ident.StringID("testns1") 47 defaultTestNs2ID = ident.StringID("testns2") 48 defaultTestRetentionOpts = retention.NewOptions(). 49 SetBufferFuture(10 * time.Minute). 50 SetBufferPast(10 * time.Minute). 51 SetBlockSize(2 * time.Hour). 52 SetRetentionPeriod(48 * time.Hour) 53 defaultTestNs2RetentionOpts = defaultTestRetentionOpts. 54 SetBlockSize(4 * time.Hour) 55 defaultTestAggregationOpts = namespace.NewAggregationOptions(). 56 SetAggregations([]namespace.Aggregation{namespace.NewUnaggregatedAggregation()}) 57 defaultTestNs2AggregationOpts = namespace.NewAggregationOptions(). 58 SetAggregations([]namespace.Aggregation{namespace.NewAggregatedAggregation( 59 namespace.AggregatedAttributes{ 60 Resolution: 1 * time.Minute, 61 DownsampleOptions: namespace.NewDownsampleOptions(true), 62 }), 63 }) 64 defaultTestNs1Opts = newNamespaceOptions().SetRetentionOptions(defaultTestRetentionOpts). 65 SetAggregationOptions(defaultTestAggregationOpts) 66 defaultTestNs2Opts = newNamespaceOptions().SetRetentionOptions(defaultTestNs2RetentionOpts). 67 SetAggregationOptions(defaultTestNs2AggregationOpts) 68 ) 69 70 func newNamespaceOptions() namespace.Options { 71 state, err := namespace.NewStagingState(nsproto.StagingStatus_READY) 72 if err != nil { 73 panic("error creating staging state") 74 } 75 return namespace.NewOptions().SetStagingState(state) 76 } 77 78 func TestDynamicClustersUninitialized(t *testing.T) { 79 t.Parallel() 80 81 ctrl := xtest.NewController(t) 82 defer ctrl.Finish() 83 84 mockSession := client.NewMockSession(ctrl) 85 86 // setup dynamic cluster without any namespaces 87 mapCh := make(nsMapCh, 10) 88 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, false) 89 90 cfg := DynamicClusterNamespaceConfiguration{ 91 session: mockSession, 92 nsInitializer: nsInitializer, 93 } 94 95 opts := newTestOptions(cfg) 96 97 clusters, err := NewDynamicClusters(opts) 98 require.NoError(t, err) 99 100 //nolint:errcheck 101 defer clusters.Close() 102 103 // Aggregated namespaces should not exist 104 _, ok := clusters.AggregatedClusterNamespace(RetentionResolution{ 105 Retention: 48 * time.Hour, 106 Resolution: 1 * time.Minute, 107 }) 108 require.False(t, ok) 109 110 // Unaggregated namespaces should not be initialized 111 _, ok = clusters.UnaggregatedClusterNamespace() 112 require.False(t, ok) 113 114 // Cluster namespaces should be empty 115 require.Len(t, clusters.ClusterNamespaces(), 0) 116 } 117 118 func TestDynamicClustersInitialization(t *testing.T) { 119 ctrl := xtest.NewController(t) 120 defer ctrl.Finish() 121 122 mockSession := client.NewMockSession(ctrl) 123 124 mapCh := make(nsMapCh, 10) 125 mapCh <- testNamespaceMap(t, []mapParams{ 126 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 127 {nsID: defaultTestNs2ID, nsOpts: defaultTestNs2Opts}, 128 }) 129 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, true) 130 131 cfg := DynamicClusterNamespaceConfiguration{ 132 session: mockSession, 133 nsInitializer: nsInitializer, 134 } 135 136 opts := newTestOptions(cfg) 137 138 clusters, err := NewDynamicClusters(opts) 139 require.NoError(t, err) 140 141 defer clusters.Close() 142 143 requireClusterNamespace(t, clusters, defaultTestNs2ID, ClusterNamespaceOptions{ 144 attributes: storagemetadata.Attributes{ 145 MetricsType: storagemetadata.AggregatedMetricsType, 146 Retention: 48 * time.Hour, 147 Resolution: 1 * time.Minute, 148 }, 149 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 150 }) 151 152 requireClusterNamespace(t, clusters, defaultTestNs1ID, ClusterNamespaceOptions{ 153 attributes: storagemetadata.Attributes{ 154 MetricsType: storagemetadata.UnaggregatedMetricsType, 155 Retention: 48 * time.Hour, 156 }}) 157 158 requireClusterNamespaceIDs(t, clusters, []ident.ID{defaultTestNs1ID, defaultTestNs2ID}) 159 } 160 161 func TestDynamicClustersWithUpdates(t *testing.T) { 162 ctrl := xtest.NewController(t) 163 defer ctrl.Finish() 164 165 mockSession := client.NewMockSession(ctrl) 166 167 mapCh := make(nsMapCh, 10) 168 nsMap := testNamespaceMap(t, []mapParams{ 169 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 170 {nsID: defaultTestNs2ID, nsOpts: defaultTestNs2Opts}, 171 }) 172 mapCh <- nsMap 173 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, true) 174 175 cfg := DynamicClusterNamespaceConfiguration{ 176 session: mockSession, 177 nsInitializer: nsInitializer, 178 } 179 180 opts := newTestOptions(cfg) 181 182 clusters, err := NewDynamicClusters(opts) 183 require.NoError(t, err) 184 185 defer clusters.Close() 186 187 requireClusterNamespace(t, clusters, defaultTestNs2ID, ClusterNamespaceOptions{ 188 attributes: storagemetadata.Attributes{ 189 MetricsType: storagemetadata.AggregatedMetricsType, 190 Retention: 48 * time.Hour, 191 Resolution: 1 * time.Minute, 192 }, 193 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 194 }) 195 196 requireClusterNamespace(t, clusters, defaultTestNs1ID, ClusterNamespaceOptions{ 197 attributes: storagemetadata.Attributes{ 198 MetricsType: storagemetadata.UnaggregatedMetricsType, 199 Retention: 48 * time.Hour, 200 }}) 201 202 // Update resolution of aggregated namespace 203 newOpts := defaultTestNs2Opts. 204 SetAggregationOptions(defaultTestNs2AggregationOpts. 205 SetAggregations([]namespace.Aggregation{namespace.NewAggregatedAggregation( 206 namespace.AggregatedAttributes{ 207 Resolution: 2 * time.Minute, 208 DownsampleOptions: namespace.NewDownsampleOptions(true), 209 }), 210 })) 211 nsMap = testNamespaceMap(t, []mapParams{ 212 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 213 {nsID: defaultTestNs2ID, nsOpts: newOpts}, 214 }) 215 216 // Send update to trigger watch 217 mapCh <- nsMap 218 219 require.True(t, xclock.WaitUntil(func() bool { 220 found := assertClusterNamespace(clusters, defaultTestNs2ID, ClusterNamespaceOptions{ 221 attributes: storagemetadata.Attributes{ 222 MetricsType: storagemetadata.AggregatedMetricsType, 223 Retention: 48 * time.Hour, 224 Resolution: 2 * time.Minute, 225 }, 226 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 227 }) 228 229 return found && assertClusterNamespaceIDs(clusters.ClusterNamespaces(), []ident.ID{defaultTestNs1ID, defaultTestNs2ID}) 230 }, time.Second)) 231 } 232 233 func TestDynamicClustersWithMultipleInitializers(t *testing.T) { 234 ctrl := xtest.NewController(t) 235 defer ctrl.Finish() 236 237 mockSession := client.NewMockSession(ctrl) 238 mockSession2 := client.NewMockSession(ctrl) 239 240 mapCh := make(nsMapCh, 10) 241 mapCh <- testNamespaceMap(t, []mapParams{ 242 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 243 {nsID: defaultTestNs2ID, nsOpts: defaultTestNs2Opts}, 244 }) 245 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, true) 246 247 fooOpts := defaultTestNs1Opts. 248 SetAggregationOptions(namespace.NewAggregationOptions(). 249 SetAggregations([]namespace.Aggregation{namespace.NewAggregatedAggregation( 250 namespace.AggregatedAttributes{ 251 Resolution: 2 * time.Minute, 252 DownsampleOptions: namespace.NewDownsampleOptions(true), 253 }), 254 })) 255 barOpts := defaultTestNs1Opts. 256 SetAggregationOptions(namespace.NewAggregationOptions(). 257 SetAggregations([]namespace.Aggregation{namespace.NewAggregatedAggregation( 258 namespace.AggregatedAttributes{ 259 Resolution: 5 * time.Minute, 260 DownsampleOptions: namespace.NewDownsampleOptions(false), 261 }), 262 })) 263 var ( 264 fooNsID = ident.StringID("foo") 265 barNsID = ident.StringID("bar") 266 ) 267 nsMap := testNamespaceMap(t, []mapParams{ 268 {nsID: fooNsID, nsOpts: fooOpts}, 269 {nsID: barNsID, nsOpts: barOpts}, 270 }) 271 272 mapCh2 := make(nsMapCh, 10) 273 mapCh2 <- nsMap 274 nsInitializer2 := newFakeNsInitializer(t, ctrl, mapCh2, true) 275 276 cfg := DynamicClusterNamespaceConfiguration{ 277 session: mockSession, 278 nsInitializer: nsInitializer, 279 } 280 cfg2 := DynamicClusterNamespaceConfiguration{ 281 session: mockSession2, 282 nsInitializer: nsInitializer2, 283 } 284 285 opts := newTestOptions(cfg, cfg2) 286 287 clusters, err := NewDynamicClusters(opts) 288 require.NoError(t, err) 289 290 defer clusters.Close() 291 292 requireClusterNamespace(t, clusters, defaultTestNs2ID, ClusterNamespaceOptions{ 293 attributes: storagemetadata.Attributes{ 294 MetricsType: storagemetadata.AggregatedMetricsType, 295 Retention: 48 * time.Hour, 296 Resolution: 1 * time.Minute, 297 }, 298 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 299 }) 300 301 requireClusterNamespace(t, clusters, fooNsID, ClusterNamespaceOptions{ 302 attributes: storagemetadata.Attributes{ 303 MetricsType: storagemetadata.AggregatedMetricsType, 304 Retention: 48 * time.Hour, 305 Resolution: 2 * time.Minute, 306 }, 307 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 308 }) 309 310 requireClusterNamespace(t, clusters, barNsID, ClusterNamespaceOptions{ 311 attributes: storagemetadata.Attributes{ 312 MetricsType: storagemetadata.AggregatedMetricsType, 313 Retention: 48 * time.Hour, 314 Resolution: 5 * time.Minute, 315 }, 316 downsample: &ClusterNamespaceDownsampleOptions{All: false}, 317 }) 318 319 requireClusterNamespace(t, clusters, defaultTestNs1ID, ClusterNamespaceOptions{ 320 attributes: storagemetadata.Attributes{ 321 MetricsType: storagemetadata.UnaggregatedMetricsType, 322 Retention: 48 * time.Hour, 323 }}) 324 325 requireClusterNamespaceIDs(t, clusters, []ident.ID{defaultTestNs1ID, defaultTestNs2ID, 326 fooNsID, barNsID}) 327 } 328 329 func TestDynamicClustersNonReadyNamespace(t *testing.T) { 330 ctrl := xtest.NewController(t) 331 defer ctrl.Finish() 332 333 mockSession := client.NewMockSession(ctrl) 334 335 state, err := namespace.NewStagingState(nsproto.StagingStatus_INITIALIZING) 336 require.NoError(t, err) 337 338 state2, err := namespace.NewStagingState(nsproto.StagingStatus_UNKNOWN) 339 require.NoError(t, err) 340 341 mapCh := make(nsMapCh, 10) 342 mapCh <- testNamespaceMap(t, []mapParams{ 343 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 344 {nsID: defaultTestNs2ID, nsOpts: defaultTestNs2Opts.SetStagingState(state)}, 345 {nsID: ident.StringID("foo"), nsOpts: defaultTestNs2Opts.SetStagingState(state2)}, 346 }) 347 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, true) 348 349 cfg := DynamicClusterNamespaceConfiguration{ 350 session: mockSession, 351 nsInitializer: nsInitializer, 352 } 353 354 opts := newTestOptions(cfg) 355 356 clusters, err := NewDynamicClusters(opts) 357 require.NoError(t, err) 358 359 defer clusters.Close() 360 361 requireClusterNamespace(t, clusters, defaultTestNs1ID, ClusterNamespaceOptions{ 362 attributes: storagemetadata.Attributes{ 363 MetricsType: storagemetadata.UnaggregatedMetricsType, 364 Retention: 48 * time.Hour, 365 }}) 366 367 requireClusterNamespaceIDs(t, clusters, []ident.ID{defaultTestNs1ID}) 368 requireNonReadyClusterNamespaceIDs(t, clusters, []ident.ID{defaultTestNs2ID, ident.StringID("foo")}) 369 } 370 371 func TestDynamicClustersEmptyNamespacesThenUpdates(t *testing.T) { 372 ctrl := xtest.NewController(t) 373 defer ctrl.Finish() 374 375 mockSession := client.NewMockSession(ctrl) 376 377 mapCh := make(nsMapCh, 10) 378 nsInitializer := newFakeNsInitializer(t, ctrl, mapCh, false) 379 380 cfg := DynamicClusterNamespaceConfiguration{ 381 session: mockSession, 382 nsInitializer: nsInitializer, 383 } 384 385 opts := newTestOptions(cfg) 386 387 clusters, err := NewDynamicClusters(opts) 388 require.NoError(t, err) 389 390 defer clusters.Close() 391 392 requireClusterNamespaceIDs(t, clusters, []ident.ID{}) 393 394 // Send update to trigger watch and add namespaces. 395 nsMap := testNamespaceMap(t, []mapParams{ 396 {nsID: defaultTestNs1ID, nsOpts: defaultTestNs1Opts}, 397 {nsID: defaultTestNs2ID, nsOpts: defaultTestNs2Opts}, 398 }) 399 mapCh <- nsMap 400 401 require.True(t, xclock.WaitUntil(func() bool { 402 found := assertClusterNamespace(clusters, defaultTestNs2ID, ClusterNamespaceOptions{ 403 attributes: storagemetadata.Attributes{ 404 MetricsType: storagemetadata.AggregatedMetricsType, 405 Retention: 48 * time.Hour, 406 Resolution: 1 * time.Minute, 407 }, 408 downsample: &ClusterNamespaceDownsampleOptions{All: true}, 409 }) 410 411 found = found && assertClusterNamespace(clusters, defaultTestNs1ID, ClusterNamespaceOptions{ 412 attributes: storagemetadata.Attributes{ 413 MetricsType: storagemetadata.UnaggregatedMetricsType, 414 Retention: 48 * time.Hour, 415 }}) 416 417 return found && assertClusterNamespaceIDs(clusters.ClusterNamespaces(), []ident.ID{defaultTestNs1ID, defaultTestNs2ID}) 418 }, time.Second)) 419 } 420 421 func TestDynamicClustersInitFailures(t *testing.T) { 422 ctrl := xtest.NewController(t) 423 defer ctrl.Finish() 424 425 mockSession := client.NewMockSession(ctrl) 426 427 reg := namespace.NewMockRegistry(ctrl) 428 reg.EXPECT().Watch().Return(nil, errors.New("failed to init")).AnyTimes() 429 430 cfg := DynamicClusterNamespaceConfiguration{ 431 session: mockSession, 432 nsInitializer: &fakeNsInitializer{ 433 registry: reg, 434 }, 435 } 436 437 opts := newTestOptions(cfg) 438 439 _, err := NewDynamicClusters(opts) 440 require.Error(t, err) 441 } 442 443 func newTestOptions(cfgs ...DynamicClusterNamespaceConfiguration) DynamicClusterOptions { 444 return NewDynamicClusterOptions(). 445 SetDynamicClusterNamespaceConfiguration(cfgs). 446 SetInstrumentOptions(instrument.NewOptions()). 447 SetClusterNamespacesWatcher(NewClusterNamespacesWatcher()) 448 } 449 450 func requireClusterNamespace(t *testing.T, clusters Clusters, expectedID ident.ID, expectedOpts ClusterNamespaceOptions) { 451 require.True(t, assertClusterNamespace(clusters, expectedID, expectedOpts)) 452 } 453 454 func assertClusterNamespace(clusters Clusters, expectedID ident.ID, expectedOpts ClusterNamespaceOptions) bool { 455 var ( 456 ns ClusterNamespace 457 ok bool 458 ) 459 if expectedOpts.Attributes().MetricsType == storagemetadata.AggregatedMetricsType { 460 if ns, ok = clusters.AggregatedClusterNamespace(RetentionResolution{ 461 Retention: expectedOpts.Attributes().Retention, 462 Resolution: expectedOpts.Attributes().Resolution, 463 }); !ok { 464 return false 465 } 466 } else { 467 ns, ok = clusters.UnaggregatedClusterNamespace() 468 if !ok { 469 return false 470 } 471 } 472 return assert.ObjectsAreEqual(expectedID.String(), ns.NamespaceID().String()) && 473 assert.ObjectsAreEqual(expectedOpts, ns.Options()) 474 } 475 476 type mapParams struct { 477 nsID ident.ID 478 nsOpts namespace.Options 479 } 480 481 func requireNonReadyClusterNamespaceIDs(t *testing.T, clusters Clusters, ids []ident.ID) { 482 require.True(t, assertClusterNamespaceIDs(clusters.NonReadyClusterNamespaces(), ids)) 483 } 484 485 func requireClusterNamespaceIDs(t *testing.T, clusters Clusters, ids []ident.ID) { 486 require.True(t, assertClusterNamespaceIDs(clusters.ClusterNamespaces(), ids)) 487 } 488 489 func assertClusterNamespaceIDs(actual ClusterNamespaces, ids []ident.ID) bool { 490 var ( 491 nsIds = make([]string, 0, len(ids)) 492 expectedIds = make([]string, 0, len(ids)) 493 ) 494 for _, ns := range actual { 495 nsIds = append(nsIds, ns.NamespaceID().String()) 496 } 497 for _, id := range ids { 498 expectedIds = append(expectedIds, id.String()) 499 } 500 sort.Strings(nsIds) 501 sort.Strings(expectedIds) 502 return assert.ObjectsAreEqual(expectedIds, nsIds) 503 } 504 505 func testNamespaceMap(t *testing.T, params []mapParams) namespace.Map { 506 var mds []namespace.Metadata 507 for _, param := range params { 508 md, err := namespace.NewMetadata(param.nsID, param.nsOpts) 509 require.NoError(t, err) 510 mds = append(mds, md) 511 } 512 nsMap, err := namespace.NewMap(mds) 513 require.NoError(t, err) 514 return nsMap 515 } 516 517 type nsMapCh chan namespace.Map 518 519 type fakeNsInitializer struct { 520 registry *namespace.MockRegistry 521 } 522 523 func (m *fakeNsInitializer) Init() (namespace.Registry, error) { 524 return m.registry, nil 525 } 526 527 func newFakeNsInitializer( 528 t *testing.T, 529 ctrl *gomock.Controller, 530 nsMapCh nsMapCh, 531 withInitialValue bool, 532 ) *fakeNsInitializer { 533 watch := xwatch.NewWatchable() 534 535 if withInitialValue { 536 initialValue := <-nsMapCh 537 err := watch.Update(initialValue) 538 require.NoError(t, err) 539 } 540 541 go func() { 542 for { 543 v, ok := <-nsMapCh 544 if !ok { // closed channel 545 return 546 } 547 548 err := watch.Update(v) 549 require.NoError(t, err) 550 } 551 }() 552 553 _, w, err := watch.Watch() 554 require.NoError(t, err) 555 556 nsWatch := namespace.NewWatch(w) 557 reg := namespace.NewMockRegistry(ctrl) 558 reg.EXPECT().Watch().Return(nsWatch, nil).AnyTimes() 559 560 return &fakeNsInitializer{ 561 registry: reg, 562 } 563 }