github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/namespace/dynamic_test.go (about) 1 // Copyright (c) 2017 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 namespace 22 23 import ( 24 "fmt" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cluster/client" 29 "github.com/m3db/m3/src/cluster/kv" 30 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 31 "github.com/m3db/m3/src/x/instrument" 32 xtime "github.com/m3db/m3/src/x/time" 33 34 "github.com/fortytw2/leaktest" 35 "github.com/golang/mock/gomock" 36 "github.com/golang/protobuf/proto" 37 "github.com/stretchr/testify/require" 38 "github.com/uber-go/tally" 39 ) 40 41 func newTestSetup(t *testing.T, ctrl *gomock.Controller, watchable kv.ValueWatchable) (DynamicOptions, *kv.MockStore) { 42 _, watch, err := watchable.Watch() 43 require.NoError(t, err) 44 45 ts := tally.NewTestScope("", nil) 46 mockKVStore := kv.NewMockStore(ctrl) 47 mockKVStore.EXPECT().Watch(defaultNsRegistryKey).Return(watch, nil) 48 49 mockCSClient := client.NewMockClient(ctrl) 50 mockCSClient.EXPECT().KV().Return(mockKVStore, nil) 51 52 opts := NewDynamicOptions(). 53 SetInstrumentOptions( 54 instrument.NewOptions(). 55 SetReportInterval(10 * time.Millisecond). 56 SetMetricsScope(ts)). 57 SetConfigServiceClient(mockCSClient) 58 59 return opts, mockKVStore 60 } 61 62 func numInvalidUpdates(opts DynamicOptions) int64 { 63 scope := opts.InstrumentOptions().MetricsScope().(tally.TestScope) 64 count, ok := scope.Snapshot().Counters()["namespace-registry.invalid-update+"] 65 if !ok { 66 return 0 67 } 68 return count.Value() 69 } 70 71 func currentVersionMetrics(opts DynamicOptions) float64 { 72 scope := opts.InstrumentOptions().MetricsScope().(tally.TestScope) 73 g, ok := scope.Snapshot().Gauges()["namespace-registry.current-version+"] 74 if !ok { 75 return 0.0 76 } 77 return g.Value() 78 } 79 80 func TestInitializerNoTimeout(t *testing.T) { 81 defer leaktest.CheckTimeout(t, time.Second)() 82 83 ctrl := gomock.NewController(t) 84 defer ctrl.Finish() 85 86 value := singleTestValue() 87 expectedNsValue := value.Namespaces["testns1"] 88 w := newTestWatchable(t, value) 89 defer w.Close() 90 91 opts, _ := newTestSetup(t, ctrl, w) 92 init := NewDynamicInitializer(opts) 93 reg, err := init.Init() 94 require.NoError(t, err) 95 96 requireNamespaceInRegistry(t, reg, expectedNsValue) 97 98 require.NoError(t, reg.Close()) 99 } 100 101 func TestInitializerUpdateWithBadProto(t *testing.T) { 102 defer leaktest.CheckTimeout(t, time.Second)() 103 104 ctrl := gomock.NewController(t) 105 defer ctrl.Finish() 106 107 w := newTestWatchable(t, singleTestValue()) 108 defer w.Close() 109 110 opts, _ := newTestSetup(t, ctrl, w) 111 init := NewDynamicInitializer(opts) 112 113 reg, err := init.Init() 114 require.NoError(t, err) 115 116 rmap, err := reg.Watch() 117 require.NoError(t, err) 118 require.Len(t, rmap.Get().Metadatas(), 1) 119 require.Equal(t, int64(0), numInvalidUpdates(opts)) 120 121 // update with bad proto 122 require.NoError(t, w.Update(&testValue{ 123 version: 2, 124 Registry: nsproto.Registry{ 125 Namespaces: map[string]*nsproto.NamespaceOptions{ 126 "testns1": nil, 127 "testns2": nil, 128 }, 129 }, 130 })) 131 132 time.Sleep(20 * time.Millisecond) 133 require.Equal(t, int64(1), numInvalidUpdates(opts)) 134 135 require.Len(t, rmap.Get().Metadatas(), 1) 136 require.NoError(t, reg.Close()) 137 } 138 139 func TestInitializerUpdateWithOlderVersion(t *testing.T) { 140 defer leaktest.CheckTimeout(t, time.Second)() 141 142 ctrl := gomock.NewController(t) 143 defer ctrl.Finish() 144 145 initValue := singleTestValue() 146 w := newTestWatchable(t, initValue) 147 defer w.Close() 148 149 opts, _ := newTestSetup(t, ctrl, w) 150 init := NewDynamicInitializer(opts) 151 152 reg, err := init.Init() 153 require.NoError(t, err) 154 155 rmap, err := reg.Watch() 156 require.NoError(t, err) 157 require.Len(t, rmap.Get().Metadatas(), 1) 158 require.Equal(t, int64(0), numInvalidUpdates(opts)) 159 160 // update with bad version 161 require.NoError(t, w.Update(&testValue{ 162 version: 1, 163 Registry: initValue.Registry, 164 })) 165 166 time.Sleep(20 * time.Millisecond) 167 require.Equal(t, int64(1), numInvalidUpdates(opts)) 168 169 require.Len(t, rmap.Get().Metadatas(), 1) 170 require.NoError(t, reg.Close()) 171 } 172 173 func TestInitializerUpdateWithNilValue(t *testing.T) { 174 defer leaktest.CheckTimeout(t, time.Second)() 175 176 ctrl := gomock.NewController(t) 177 defer ctrl.Finish() 178 179 w := newTestWatchable(t, singleTestValue()) 180 defer w.Close() 181 182 opts, _ := newTestSetup(t, ctrl, w) 183 init := NewDynamicInitializer(opts) 184 185 reg, err := init.Init() 186 require.NoError(t, err) 187 188 rmap, err := reg.Watch() 189 require.NoError(t, err) 190 require.Len(t, rmap.Get().Metadatas(), 1) 191 require.Equal(t, int64(0), numInvalidUpdates(opts)) 192 193 // update with nil value 194 require.NoError(t, w.Update(nil)) 195 196 time.Sleep(20 * time.Millisecond) 197 require.Equal(t, int64(1), numInvalidUpdates(opts)) 198 199 require.Len(t, rmap.Get().Metadatas(), 1) 200 require.NoError(t, reg.Close()) 201 } 202 203 func TestInitializerUpdateWithNilInitialValue(t *testing.T) { 204 defer leaktest.CheckTimeout(t, time.Second)() 205 206 ctrl := gomock.NewController(t) 207 defer ctrl.Finish() 208 209 w := newTestWatchable(t, nil) 210 defer w.Close() 211 212 opts, _ := newTestSetup(t, ctrl, w) 213 init := NewDynamicInitializer(opts) 214 215 require.NoError(t, w.Update(nil)) 216 _, err := init.Init() 217 require.Error(t, err) 218 } 219 220 func TestInitializerUpdateWithIdenticalValue(t *testing.T) { 221 defer leaktest.CheckTimeout(t, time.Second)() 222 223 ctrl := gomock.NewController(t) 224 defer ctrl.Finish() 225 226 initValue := singleTestValue() 227 w := newTestWatchable(t, initValue) 228 defer w.Close() 229 230 opts, _ := newTestSetup(t, ctrl, w) 231 init := NewDynamicInitializer(opts) 232 233 reg, err := init.Init() 234 require.NoError(t, err) 235 236 rmap, err := reg.Watch() 237 require.NoError(t, err) 238 require.Len(t, rmap.Get().Metadatas(), 1) 239 require.Equal(t, int64(0), numInvalidUpdates(opts)) 240 241 // update with new version 242 require.NoError(t, w.Update(&testValue{ 243 version: 2, 244 Registry: initValue.Registry, 245 })) 246 247 time.Sleep(20 * time.Millisecond) 248 require.Equal(t, int64(1), numInvalidUpdates(opts)) 249 250 require.Len(t, rmap.Get().Metadatas(), 1) 251 require.NoError(t, reg.Close()) 252 } 253 254 func TestInitializerUpdateSuccess(t *testing.T) { 255 defer leaktest.CheckTimeout(t, time.Second)() 256 257 ctrl := gomock.NewController(t) 258 defer ctrl.Finish() 259 260 initValue := singleTestValue() 261 w := newTestWatchable(t, initValue) 262 defer w.Close() 263 264 opts, _ := newTestSetup(t, ctrl, w) 265 init := NewDynamicInitializer(opts) 266 267 reg, err := init.Init() 268 require.NoError(t, err) 269 270 rmap, err := reg.Watch() 271 require.NoError(t, err) 272 require.Len(t, rmap.Get().Metadatas(), 1) 273 require.Equal(t, int64(0), numInvalidUpdates(opts)) 274 require.Equal(t, 0., currentVersionMetrics(opts)) 275 276 // update with valid value 277 require.NoError(t, w.Update(&testValue{ 278 version: 2, 279 Registry: nsproto.Registry{ 280 Namespaces: map[string]*nsproto.NamespaceOptions{ 281 "testns1": initValue.Namespaces["testns1"], 282 "testns2": initValue.Namespaces["testns1"], 283 }, 284 }, 285 })) 286 287 for { 288 time.Sleep(20 * time.Millisecond) 289 if numInvalidUpdates(opts) != 0 { 290 continue 291 } 292 if currentVersionMetrics(opts) != 2. { 293 continue 294 } 295 if len(rmap.Get().Metadatas()) != 2 { 296 continue 297 } 298 break 299 } 300 require.NoError(t, reg.Close()) 301 } 302 303 func TestInitializerAllowEmptyEnabled_EmptyRegistry(t *testing.T) { 304 defer leaktest.CheckTimeout(t, time.Second)() 305 306 ctrl := gomock.NewController(t) 307 defer ctrl.Finish() 308 309 w := newTestWatchable(t, nil) 310 defer w.Close() 311 312 opts, mockKV := newTestSetup(t, ctrl, w) 313 opts = opts.SetAllowEmptyInitialNamespaceRegistry(true) 314 315 mockKV.EXPECT().Get(defaultNsRegistryKey).Return(nil, kv.ErrNotFound) 316 317 init := NewDynamicInitializer(opts) 318 reg, err := init.Init() 319 require.NoError(t, err) 320 321 rw, err := reg.Watch() 322 require.NoError(t, err) 323 rMap := rw.Get() 324 require.Nil(t, rMap) 325 326 // Trigger update to add namespace 327 value := singleTestValue() 328 expectedNsValue := value.Namespaces["testns1"] 329 w.Update(value) 330 331 <-rw.C() 332 333 requireNamespaceInRegistry(t, reg, expectedNsValue) 334 335 require.NoError(t, rw.Close()) 336 require.NoError(t, reg.Close()) 337 } 338 339 func TestInitializerAllowEmptyEnabled_ExistingRegistry(t *testing.T) { 340 defer leaktest.CheckTimeout(t, time.Second)() 341 342 ctrl := gomock.NewController(t) 343 defer ctrl.Finish() 344 345 value := singleTestValue() 346 expectedNsValue := value.Namespaces["testns1"] 347 w := newTestWatchable(t, value) 348 defer w.Close() 349 350 opts, mockKV := newTestSetup(t, ctrl, w) 351 opts = opts.SetAllowEmptyInitialNamespaceRegistry(true) 352 353 mockKV.EXPECT().Get(defaultNsRegistryKey).Return(value, nil) 354 355 init := NewDynamicInitializer(opts) 356 reg, err := init.Init() 357 require.NoError(t, err) 358 359 requireNamespaceInRegistry(t, reg, expectedNsValue) 360 361 require.NoError(t, reg.Close()) 362 } 363 364 func requireNamespaceInRegistry(t *testing.T, reg Registry, expectedNsValue *nsproto.NamespaceOptions) { 365 rw, err := reg.Watch() 366 require.NoError(t, err) 367 rMap := rw.Get() 368 mds := rMap.Metadatas() 369 require.Len(t, mds, 1) 370 md := mds[0] 371 require.Equal(t, "testns1", md.ID().String()) 372 require.Equal(t, expectedNsValue.BootstrapEnabled, md.Options().BootstrapEnabled()) 373 require.Equal(t, expectedNsValue.CleanupEnabled, md.Options().CleanupEnabled()) 374 require.Equal(t, expectedNsValue.FlushEnabled, md.Options().FlushEnabled()) 375 require.Equal(t, expectedNsValue.RepairEnabled, md.Options().RepairEnabled()) 376 require.Equal(t, expectedNsValue.WritesToCommitLog, md.Options().WritesToCommitLog()) 377 378 ropts := expectedNsValue.RetentionOptions 379 observedRopts := md.Options().RetentionOptions() 380 require.Equal(t, ropts.BlockDataExpiry, observedRopts.BlockDataExpiry()) 381 require.Equal(t, ropts.BlockDataExpiryAfterNotAccessPeriodNanos, 382 toNanosInt64(observedRopts.BlockDataExpiryAfterNotAccessedPeriod())) 383 require.Equal(t, ropts.BlockSizeNanos, toNanosInt64(observedRopts.BlockSize())) 384 require.Equal(t, ropts.BufferFutureNanos, toNanosInt64(observedRopts.BufferFuture())) 385 require.Equal(t, ropts.BufferPastNanos, toNanosInt64(observedRopts.BufferPast())) 386 387 latest, found := md.Options().SchemaHistory().GetLatest() 388 require.True(t, found) 389 require.EqualValues(t, "third", latest.DeployId()) 390 391 require.NoError(t, rw.Close()) 392 } 393 394 func singleTestValue() *testValue { 395 return &testValue{ 396 version: 1, 397 Registry: nsproto.Registry{ 398 Namespaces: map[string]*nsproto.NamespaceOptions{ 399 "testns1": &nsproto.NamespaceOptions{ 400 BootstrapEnabled: true, 401 CleanupEnabled: true, 402 FlushEnabled: true, 403 RepairEnabled: true, 404 WritesToCommitLog: true, 405 RetentionOptions: &nsproto.RetentionOptions{ 406 BlockDataExpiry: true, 407 BlockDataExpiryAfterNotAccessPeriodNanos: toNanosInt64(time.Minute), 408 BlockSizeNanos: toNanosInt64(time.Hour * 2), 409 RetentionPeriodNanos: toNanosInt64(time.Hour * 48), 410 BufferFutureNanos: toNanosInt64(time.Minute * 10), 411 BufferPastNanos: toNanosInt64(time.Minute * 15), 412 }, 413 SchemaOptions: testSchemaOptions, 414 }, 415 }, 416 }, 417 } 418 } 419 420 type testValue struct { 421 nsproto.Registry 422 version int 423 } 424 425 func (v *testValue) Unmarshal(msg proto.Message) error { 426 reg, ok := msg.(*nsproto.Registry) 427 if !ok { 428 return fmt.Errorf("incorrect type provided: %T", msg) 429 } 430 reg.Namespaces = v.Namespaces 431 return nil 432 } 433 434 func (v *testValue) Version() int { 435 return v.version 436 } 437 438 func (v *testValue) IsNewer(other kv.Value) bool { 439 return v.Version() > other.Version() 440 } 441 442 func newTestWatchable(t *testing.T, initValue *testValue) kv.ValueWatchable { 443 w := kv.NewValueWatchable() 444 if initValue != nil { 445 require.NoError(t, w.Update(initValue)) 446 } 447 return w 448 } 449 450 func toNanosInt64(t time.Duration) int64 { 451 return xtime.ToNormalizedDuration(t, time.Nanosecond) 452 }