github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/namespace/convert_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_test 22 23 import ( 24 "testing" 25 "time" 26 27 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 28 "github.com/m3db/m3/src/dbnode/namespace" 29 "github.com/m3db/m3/src/dbnode/retention" 30 "github.com/m3db/m3/src/x/ident" 31 xtest "github.com/m3db/m3/src/x/test" 32 33 protobuftypes "github.com/gogo/protobuf/types" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 ) 37 38 var ( 39 testSchemaOptions = namespace.GenTestSchemaOptions("mainpkg/main.proto", "testdata") 40 41 toNanos = func(mins int64) int64 { 42 return int64(time.Duration(mins) * time.Minute / time.Nanosecond) 43 } 44 45 validIndexOpts = nsproto.IndexOptions{ 46 Enabled: true, 47 BlockSizeNanos: toNanos(600), // 10h 48 } 49 50 validRetentionOpts = nsproto.RetentionOptions{ 51 RetentionPeriodNanos: toNanos(1200), // 20h 52 BlockSizeNanos: toNanos(120), // 2h 53 BufferFutureNanos: toNanos(12), // 12m 54 BufferPastNanos: toNanos(10), // 10m 55 BlockDataExpiry: true, 56 BlockDataExpiryAfterNotAccessPeriodNanos: toNanos(30), // 30m 57 } 58 59 validExtendedOpts = xtest.NewTestExtendedOptionsProto("foo") 60 61 validAggregationOpts = nsproto.AggregationOptions{ 62 Aggregations: []*nsproto.Aggregation{ 63 {Aggregated: false}, 64 }, 65 } 66 67 validNamespaceOpts = []nsproto.NamespaceOptions{ 68 { 69 BootstrapEnabled: true, 70 FlushEnabled: true, 71 WritesToCommitLog: true, 72 CleanupEnabled: true, 73 RepairEnabled: true, 74 CacheBlocksOnRetrieve: &protobuftypes.BoolValue{Value: false}, 75 RetentionOptions: &validRetentionOpts, 76 SchemaOptions: testSchemaOptions, 77 ExtendedOptions: validExtendedOpts, 78 StagingState: &nsproto.StagingState{Status: nsproto.StagingStatus_INITIALIZING}, 79 }, 80 { 81 BootstrapEnabled: true, 82 FlushEnabled: true, 83 WritesToCommitLog: true, 84 CleanupEnabled: true, 85 RepairEnabled: true, 86 // Explicitly not setting CacheBlocksOnRetrieve here to test defaulting to true when not set. 87 RetentionOptions: &validRetentionOpts, 88 IndexOptions: &validIndexOpts, 89 AggregationOptions: &validAggregationOpts, 90 }, 91 } 92 93 validNamespaceSchemaOpts = []nsproto.NamespaceOptions{ 94 { 95 RetentionOptions: &validRetentionOpts, 96 SchemaOptions: testSchemaOptions, 97 }, 98 } 99 100 invalidRetentionOpts = []nsproto.RetentionOptions{ 101 // block size < buffer past 102 { 103 RetentionPeriodNanos: toNanos(1200), // 20h 104 BlockSizeNanos: toNanos(2), // 2m 105 BufferFutureNanos: toNanos(12), // 12m 106 BufferPastNanos: toNanos(10), // 10m 107 BlockDataExpiry: true, 108 BlockDataExpiryAfterNotAccessPeriodNanos: toNanos(30), // 30m 109 }, 110 // block size > retention 111 { 112 RetentionPeriodNanos: toNanos(1200), // 20h 113 BlockSizeNanos: toNanos(1260), // 21h 114 BufferFutureNanos: toNanos(12), // 12m 115 BufferPastNanos: toNanos(10), // 10m 116 BlockDataExpiry: true, 117 BlockDataExpiryAfterNotAccessPeriodNanos: toNanos(30), // 30m 118 }, 119 } 120 121 invalidAggregationOpts = nsproto.AggregationOptions{ 122 Aggregations: []*nsproto.Aggregation{ 123 { 124 Aggregated: true, 125 Attributes: &nsproto.AggregatedAttributes{ 126 ResolutionNanos: -10, 127 DownsampleOptions: &nsproto.DownsampleOptions{All: true}, 128 }, 129 }, 130 }, 131 } 132 ) 133 134 func init() { 135 namespace.RegisterExtendedOptionsConverter("testExtendedOptions", xtest.ConvertToTestExtendedOptions) 136 } 137 138 func TestNamespaceToRetentionValid(t *testing.T) { 139 validOpts := validRetentionOpts 140 ropts, err := namespace.ToRetention(&validOpts) 141 require.NoError(t, err) 142 assertEqualRetentions(t, validOpts, ropts) 143 } 144 145 func TestNamespaceToRetentionInvalid(t *testing.T) { 146 for _, opts := range invalidRetentionOpts { 147 _, err := namespace.ToRetention(&opts) 148 require.Error(t, err) 149 } 150 } 151 152 func TestToMetadataValid(t *testing.T) { 153 for _, nsopts := range validNamespaceOpts { 154 nsOpts, err := namespace.ToMetadata("abc", &nsopts) 155 require.NoError(t, err) 156 assertEqualMetadata(t, "abc", nsopts, nsOpts) 157 } 158 } 159 160 func TestToMetadataNilIndexOpts(t *testing.T) { 161 nsopts := validNamespaceOpts[0] 162 163 nsopts.RetentionOptions.BlockSizeNanos = 7200000000000 / 2 164 nsopts.IndexOptions = nil 165 166 nsOpts, err := namespace.ToMetadata("id", &nsopts) 167 require.NoError(t, err) 168 assert.Equal(t, 169 time.Duration(nsopts.RetentionOptions.BlockSizeNanos), 170 nsOpts.Options().IndexOptions().BlockSize()) 171 } 172 173 func TestToMetadataInvalid(t *testing.T) { 174 for _, nsopts := range validNamespaceOpts { 175 _, err := namespace.ToMetadata("", &nsopts) 176 require.Error(t, err) 177 } 178 179 for _, nsopts := range validNamespaceOpts { 180 opts := nsopts 181 opts.RetentionOptions = nil 182 _, err := namespace.ToMetadata("abc", &opts) 183 require.Error(t, err) 184 } 185 186 for _, nsopts := range validNamespaceOpts { 187 for _, ro := range invalidRetentionOpts { 188 opts := nsopts 189 opts.RetentionOptions = &ro 190 _, err := namespace.ToMetadata("abc", &opts) 191 require.Error(t, err) 192 } 193 } 194 } 195 196 func TestFromProto(t *testing.T) { 197 validRegistry := nsproto.Registry{ 198 Namespaces: map[string]*nsproto.NamespaceOptions{ 199 "testns1": &validNamespaceOpts[0], 200 "testns2": &validNamespaceOpts[1], 201 }, 202 } 203 nsMap, err := namespace.FromProto(validRegistry) 204 require.NoError(t, err) 205 206 md1, err := nsMap.Get(ident.StringID("testns1")) 207 require.NoError(t, err) 208 assertEqualMetadata(t, "testns1", validNamespaceOpts[0], md1) 209 210 md2, err := nsMap.Get(ident.StringID("testns2")) 211 require.NoError(t, err) 212 assertEqualMetadata(t, "testns2", validNamespaceOpts[1], md2) 213 } 214 215 func TestToProto(t *testing.T) { 216 state, err := namespace.NewStagingState(nsproto.StagingStatus_READY) 217 require.NoError(t, err) 218 219 // make ns map 220 md1, err := namespace.NewMetadata(ident.StringID("ns1"), 221 namespace.NewOptions(). 222 SetBootstrapEnabled(true). 223 SetStagingState(state)) 224 require.NoError(t, err) 225 md2, err := namespace.NewMetadata(ident.StringID("ns2"), 226 namespace.NewOptions().SetBootstrapEnabled(false)) 227 require.NoError(t, err) 228 nsMap, err := namespace.NewMap([]namespace.Metadata{md1, md2}) 229 require.NoError(t, err) 230 231 // convert to nsproto map 232 reg, err := namespace.ToProto(nsMap) 233 require.NoError(t, err) 234 require.Len(t, reg.Namespaces, 2) 235 236 // NB(prateek): expected/observed are inverted here 237 assertEqualMetadata(t, "ns1", *(reg.Namespaces["ns1"]), md1) 238 assertEqualMetadata(t, "ns2", *(reg.Namespaces["ns2"]), md2) 239 } 240 241 func TestSchemaFromProto(t *testing.T) { 242 validRegistry := nsproto.Registry{ 243 Namespaces: map[string]*nsproto.NamespaceOptions{ 244 "testns1": &validNamespaceSchemaOpts[0], 245 }, 246 } 247 nsMap, err := namespace.FromProto(validRegistry) 248 require.NoError(t, err) 249 250 md1, err := nsMap.Get(ident.StringID("testns1")) 251 require.NoError(t, err) 252 assertEqualMetadata(t, "testns1", validNamespaceSchemaOpts[0], md1) 253 254 require.NotNil(t, md1.Options().SchemaHistory()) 255 testSchema, found := md1.Options().SchemaHistory().GetLatest() 256 require.True(t, found) 257 require.NotNil(t, testSchema) 258 require.EqualValues(t, "third", testSchema.DeployId()) 259 require.EqualValues(t, "TestMessage", testSchema.Get().GetName()) 260 } 261 262 func TestSchemaToProto(t *testing.T) { 263 // make ns map 264 testSchemaReg, err := namespace.LoadSchemaHistory(testSchemaOptions) 265 require.NoError(t, err) 266 md1, err := namespace.NewMetadata(ident.StringID("ns1"), 267 namespace.NewOptions().SetSchemaHistory(testSchemaReg)) 268 require.NoError(t, err) 269 nsMap, err := namespace.NewMap([]namespace.Metadata{md1}) 270 require.NoError(t, err) 271 272 // convert to nsproto map 273 reg, err := namespace.ToProto(nsMap) 274 require.NoError(t, err) 275 require.Len(t, reg.Namespaces, 1) 276 277 assertEqualMetadata(t, "ns1", *(reg.Namespaces["ns1"]), md1) 278 outSchemaReg, err := namespace.LoadSchemaHistory(reg.Namespaces["ns1"].SchemaOptions) 279 require.NoError(t, err) 280 outSchema, found := outSchemaReg.GetLatest() 281 require.True(t, found) 282 require.NotNil(t, outSchema) 283 require.EqualValues(t, "third", outSchema.DeployId()) 284 require.EqualValues(t, "TestMessage", outSchema.Get().GetName()) 285 } 286 287 func TestToProtoSnapshotEnabled(t *testing.T) { 288 md, err := namespace.NewMetadata( 289 ident.StringID("ns1"), 290 namespace.NewOptions(). 291 // Don't use default value 292 SetSnapshotEnabled(!namespace.NewOptions().SnapshotEnabled()), 293 ) 294 295 require.NoError(t, err) 296 nsMap, err := namespace.NewMap([]namespace.Metadata{md}) 297 require.NoError(t, err) 298 299 reg, err := namespace.ToProto(nsMap) 300 require.NoError(t, err) 301 require.Len(t, reg.Namespaces, 1) 302 require.Equal(t, 303 !namespace.NewOptions().SnapshotEnabled(), 304 reg.Namespaces["ns1"].SnapshotEnabled, 305 ) 306 } 307 308 func TestFromProtoSnapshotEnabled(t *testing.T) { 309 validRegistry := nsproto.Registry{ 310 Namespaces: map[string]*nsproto.NamespaceOptions{ 311 "testns1": { 312 // Use non-default value 313 SnapshotEnabled: !namespace.NewOptions().SnapshotEnabled(), 314 // Retention must be set 315 RetentionOptions: &validRetentionOpts, 316 }, 317 }, 318 } 319 nsMap, err := namespace.FromProto(validRegistry) 320 require.NoError(t, err) 321 322 md, err := nsMap.Get(ident.StringID("testns1")) 323 require.NoError(t, err) 324 require.Equal(t, !namespace.NewOptions().SnapshotEnabled(), md.Options().SnapshotEnabled()) 325 } 326 327 func TestInvalidExtendedOptions(t *testing.T) { 328 invalidExtendedOptsNoConverterForType := &nsproto.ExtendedOptions{Type: "unknown"} 329 _, err := namespace.ToExtendedOptions(invalidExtendedOptsNoConverterForType) 330 assert.EqualError(t, err, "dynamic ExtendedOptions converter not registered for type unknown") 331 332 invalidExtendedOptsConverterFailure := xtest.NewTestExtendedOptionsProto("error") 333 _, err = namespace.ToExtendedOptions(invalidExtendedOptsConverterFailure) 334 assert.EqualError(t, err, "test error in converter") 335 336 invalidExtendedOpts := xtest.NewTestExtendedOptionsProto("invalid") 337 _, err = namespace.ToExtendedOptions(invalidExtendedOpts) 338 assert.EqualError(t, err, "invalid ExtendedOptions") 339 340 invalidExtendedOptionsNoOptions := &nsproto.ExtendedOptions{Type: "testExtendedOptions"} 341 _, err = namespace.ToExtendedOptions(invalidExtendedOptionsNoOptions) 342 assert.EqualError(t, err, "extendedOptions.Options must be set") 343 } 344 345 func TestConvertExtendedOptionsNil(t *testing.T) { 346 convertedExtendedOpts, err := namespace.ToExtendedOptions(nil) 347 require.NoError(t, err) 348 require.Nil(t, convertedExtendedOpts) 349 } 350 351 func TestToAggregationOptions(t *testing.T) { 352 aggOpts, err := namespace.ToAggregationOptions(&validAggregationOpts) 353 require.NoError(t, err) 354 355 require.Equal(t, 1, len(aggOpts.Aggregations())) 356 357 aggregation := aggOpts.Aggregations()[0] 358 require.Equal(t, false, aggregation.Aggregated) 359 require.Equal(t, namespace.AggregatedAttributes{}, aggregation.Attributes) 360 } 361 362 func TestToAggregationOptionsInvalid(t *testing.T) { 363 _, err := namespace.ToAggregationOptions(&invalidAggregationOpts) 364 require.Error(t, err) 365 } 366 367 func TestAggregationOptsToProto(t *testing.T) { 368 aggOpts, err := namespace.ToAggregationOptions(&validAggregationOpts) 369 require.NoError(t, err) 370 371 // make ns map 372 md1, err := namespace.NewMetadata(ident.StringID("ns1"), 373 namespace.NewOptions().SetAggregationOptions(aggOpts)) 374 require.NoError(t, err) 375 nsMap, err := namespace.NewMap([]namespace.Metadata{md1}) 376 require.NoError(t, err) 377 378 // convert to nsproto map 379 reg, err := namespace.ToProto(nsMap) 380 require.NoError(t, err) 381 require.Len(t, reg.Namespaces, 1) 382 383 nsOpts := *reg.Namespaces["ns1"] 384 385 require.Equal(t, validAggregationOpts, *nsOpts.AggregationOptions) 386 } 387 388 func assertEqualMetadata(t *testing.T, name string, expected nsproto.NamespaceOptions, observed namespace.Metadata) { 389 require.Equal(t, name, observed.ID().String()) 390 opts := observed.Options() 391 392 expectedCacheBlocksOnRetrieve := false 393 if expected.CacheBlocksOnRetrieve != nil { 394 expectedCacheBlocksOnRetrieve = expected.CacheBlocksOnRetrieve.Value 395 } 396 397 require.Equal(t, expected.BootstrapEnabled, opts.BootstrapEnabled()) 398 require.Equal(t, expected.FlushEnabled, opts.FlushEnabled()) 399 require.Equal(t, expected.WritesToCommitLog, opts.WritesToCommitLog()) 400 require.Equal(t, expected.CleanupEnabled, opts.CleanupEnabled()) 401 require.Equal(t, expected.RepairEnabled, opts.RepairEnabled()) 402 require.Equal(t, expectedCacheBlocksOnRetrieve, opts.CacheBlocksOnRetrieve()) 403 expectedSchemaReg, err := namespace.LoadSchemaHistory(expected.SchemaOptions) 404 require.NoError(t, err) 405 require.NotNil(t, expectedSchemaReg) 406 require.True(t, expectedSchemaReg.Equal(observed.Options().SchemaHistory())) 407 408 assertEqualRetentions(t, *expected.RetentionOptions, opts.RetentionOptions()) 409 assertEqualStagingState(t, expected.StagingState, opts.StagingState()) 410 assertEqualExtendedOpts(t, expected.ExtendedOptions, opts.ExtendedOptions()) 411 } 412 413 func assertEqualRetentions(t *testing.T, expected nsproto.RetentionOptions, observed retention.Options) { 414 require.Equal(t, expected.RetentionPeriodNanos, observed.RetentionPeriod().Nanoseconds()) 415 require.Equal(t, expected.BlockSizeNanos, observed.BlockSize().Nanoseconds()) 416 require.Equal(t, expected.BufferPastNanos, observed.BufferPast().Nanoseconds()) 417 require.Equal(t, expected.BufferFutureNanos, observed.BufferFuture().Nanoseconds()) 418 require.Equal(t, expected.BlockDataExpiry, observed.BlockDataExpiry()) 419 require.Equal(t, expected.BlockDataExpiryAfterNotAccessPeriodNanos, 420 observed.BlockDataExpiryAfterNotAccessedPeriod().Nanoseconds()) 421 } 422 423 func assertEqualExtendedOpts(t *testing.T, expectedProto *nsproto.ExtendedOptions, observed namespace.ExtendedOptions) { 424 t.Helper() 425 426 if expectedProto == nil { 427 assert.Nil(t, observed) 428 return 429 } 430 431 expected, err := xtest.ConvertToTestExtendedOptions(expectedProto.Options) 432 require.NoError(t, err) 433 434 assert.Equal(t, expected, observed) 435 } 436 437 func assertEqualStagingState(t *testing.T, expected *nsproto.StagingState, observed namespace.StagingState) { 438 if expected == nil { 439 assert.Equal(t, namespace.StagingState{}, observed) 440 return 441 } 442 443 state, err := namespace.NewStagingState(expected.Status) 444 require.NoError(t, err) 445 446 require.Equal(t, state, observed) 447 }