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  }