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  }