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  }