github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/alertmanager/alertstore/store_test.go (about)

     1  package alertstore
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/go-kit/log"
     9  	"github.com/prometheus/alertmanager/cluster/clusterpb"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/thanos-io/thanos/pkg/objstore"
    13  
    14  	"github.com/cortexproject/cortex/pkg/alertmanager/alertspb"
    15  	"github.com/cortexproject/cortex/pkg/alertmanager/alertstore/bucketclient"
    16  	"github.com/cortexproject/cortex/pkg/alertmanager/alertstore/objectclient"
    17  	"github.com/cortexproject/cortex/pkg/chunk"
    18  )
    19  
    20  func TestAlertStore_ListAllUsers(t *testing.T) {
    21  	runForEachAlertStore(t, func(t *testing.T, store AlertStore, client interface{}) {
    22  		ctx := context.Background()
    23  		user1Cfg := alertspb.AlertConfigDesc{User: "user-1", RawConfig: "content-1"}
    24  		user2Cfg := alertspb.AlertConfigDesc{User: "user-2", RawConfig: "content-2"}
    25  
    26  		// The storage is empty.
    27  		{
    28  			users, err := store.ListAllUsers(ctx)
    29  			require.NoError(t, err)
    30  			assert.Empty(t, users)
    31  		}
    32  
    33  		// The storage contains users.
    34  		{
    35  			require.NoError(t, store.SetAlertConfig(ctx, user1Cfg))
    36  			require.NoError(t, store.SetAlertConfig(ctx, user2Cfg))
    37  
    38  			users, err := store.ListAllUsers(ctx)
    39  			require.NoError(t, err)
    40  			assert.ElementsMatch(t, []string{"user-1", "user-2"}, users)
    41  		}
    42  	})
    43  }
    44  
    45  func TestAlertStore_SetAndGetAlertConfig(t *testing.T) {
    46  	runForEachAlertStore(t, func(t *testing.T, store AlertStore, client interface{}) {
    47  		ctx := context.Background()
    48  		user1Cfg := alertspb.AlertConfigDesc{User: "user-1", RawConfig: "content-1"}
    49  		user2Cfg := alertspb.AlertConfigDesc{User: "user-2", RawConfig: "content-2"}
    50  
    51  		// The user has no config.
    52  		{
    53  			_, err := store.GetAlertConfig(ctx, "user-1")
    54  			assert.Equal(t, alertspb.ErrNotFound, err)
    55  		}
    56  
    57  		// The user has a config
    58  		{
    59  			require.NoError(t, store.SetAlertConfig(ctx, user1Cfg))
    60  			require.NoError(t, store.SetAlertConfig(ctx, user2Cfg))
    61  
    62  			config, err := store.GetAlertConfig(ctx, "user-1")
    63  			require.NoError(t, err)
    64  			assert.Equal(t, user1Cfg, config)
    65  
    66  			config, err = store.GetAlertConfig(ctx, "user-2")
    67  			require.NoError(t, err)
    68  			assert.Equal(t, user2Cfg, config)
    69  
    70  			// Ensure the config is stored at the expected location. Without this check
    71  			// we have no guarantee that the objects are stored at the expected location.
    72  			exists, err := objectExists(client, "alerts/user-1")
    73  			require.NoError(t, err)
    74  			assert.True(t, exists)
    75  
    76  			exists, err = objectExists(client, "alerts/user-2")
    77  			require.NoError(t, err)
    78  			assert.True(t, exists)
    79  		}
    80  	})
    81  }
    82  
    83  func TestStore_GetAlertConfigs(t *testing.T) {
    84  	runForEachAlertStore(t, func(t *testing.T, store AlertStore, client interface{}) {
    85  		ctx := context.Background()
    86  		user1Cfg := alertspb.AlertConfigDesc{User: "user-1", RawConfig: "content-1"}
    87  		user2Cfg := alertspb.AlertConfigDesc{User: "user-2", RawConfig: "content-2"}
    88  
    89  		// The storage is empty.
    90  		{
    91  			configs, err := store.GetAlertConfigs(ctx, []string{"user-1", "user-2"})
    92  			require.NoError(t, err)
    93  			assert.Empty(t, configs)
    94  		}
    95  
    96  		// The storage contains some configs.
    97  		{
    98  			require.NoError(t, store.SetAlertConfig(ctx, user1Cfg))
    99  
   100  			configs, err := store.GetAlertConfigs(ctx, []string{"user-1", "user-2"})
   101  			require.NoError(t, err)
   102  			assert.Contains(t, configs, "user-1")
   103  			assert.NotContains(t, configs, "user-2")
   104  			assert.Equal(t, user1Cfg, configs["user-1"])
   105  
   106  			// Add another user config.
   107  			require.NoError(t, store.SetAlertConfig(ctx, user2Cfg))
   108  
   109  			configs, err = store.GetAlertConfigs(ctx, []string{"user-1", "user-2"})
   110  			require.NoError(t, err)
   111  			assert.Contains(t, configs, "user-1")
   112  			assert.Contains(t, configs, "user-2")
   113  			assert.Equal(t, user1Cfg, configs["user-1"])
   114  			assert.Equal(t, user2Cfg, configs["user-2"])
   115  		}
   116  	})
   117  }
   118  
   119  func TestAlertStore_DeleteAlertConfig(t *testing.T) {
   120  	runForEachAlertStore(t, func(t *testing.T, store AlertStore, client interface{}) {
   121  		ctx := context.Background()
   122  		user1Cfg := alertspb.AlertConfigDesc{User: "user-1", RawConfig: "content-1"}
   123  		user2Cfg := alertspb.AlertConfigDesc{User: "user-2", RawConfig: "content-2"}
   124  
   125  		// Upload the config for 2 users.
   126  		require.NoError(t, store.SetAlertConfig(ctx, user1Cfg))
   127  		require.NoError(t, store.SetAlertConfig(ctx, user2Cfg))
   128  
   129  		// Ensure the config has been correctly uploaded.
   130  		config, err := store.GetAlertConfig(ctx, "user-1")
   131  		require.NoError(t, err)
   132  		assert.Equal(t, user1Cfg, config)
   133  
   134  		config, err = store.GetAlertConfig(ctx, "user-2")
   135  		require.NoError(t, err)
   136  		assert.Equal(t, user2Cfg, config)
   137  
   138  		// Delete the config for user-1.
   139  		require.NoError(t, store.DeleteAlertConfig(ctx, "user-1"))
   140  
   141  		// Ensure the correct config has been deleted.
   142  		_, err = store.GetAlertConfig(ctx, "user-1")
   143  		assert.Equal(t, alertspb.ErrNotFound, err)
   144  
   145  		config, err = store.GetAlertConfig(ctx, "user-2")
   146  		require.NoError(t, err)
   147  		assert.Equal(t, user2Cfg, config)
   148  
   149  		// Delete again (should be idempotent).
   150  		require.NoError(t, store.DeleteAlertConfig(ctx, "user-1"))
   151  	})
   152  }
   153  
   154  func runForEachAlertStore(t *testing.T, testFn func(t *testing.T, store AlertStore, client interface{})) {
   155  	legacyClient := chunk.NewMockStorage()
   156  	legacyStore := objectclient.NewAlertStore(legacyClient, log.NewNopLogger())
   157  
   158  	bucketClient := objstore.NewInMemBucket()
   159  	bucketStore := bucketclient.NewBucketAlertStore(bucketClient, nil, log.NewNopLogger())
   160  
   161  	stores := map[string]struct {
   162  		store  AlertStore
   163  		client interface{}
   164  	}{
   165  		"legacy": {store: legacyStore, client: legacyClient},
   166  		"bucket": {store: bucketStore, client: bucketClient},
   167  	}
   168  
   169  	for name, data := range stores {
   170  		t.Run(name, func(t *testing.T) {
   171  			testFn(t, data.store, data.client)
   172  		})
   173  	}
   174  }
   175  
   176  func objectExists(bucketClient interface{}, key string) (bool, error) {
   177  	if typed, ok := bucketClient.(*chunk.MockStorage); ok {
   178  		_, err := typed.GetObject(context.Background(), key)
   179  		if errors.Is(err, chunk.ErrStorageObjectNotFound) {
   180  			return false, nil
   181  		}
   182  		if err == nil {
   183  			return true, nil
   184  		}
   185  		return false, err
   186  	}
   187  
   188  	if typed, ok := bucketClient.(*objstore.InMemBucket); ok {
   189  		return typed.Exists(context.Background(), key)
   190  	}
   191  
   192  	panic("unexpected bucket client")
   193  }
   194  
   195  func makeTestFullState(content string) alertspb.FullStateDesc {
   196  	return alertspb.FullStateDesc{
   197  		State: &clusterpb.FullState{
   198  			Parts: []clusterpb.Part{
   199  				{
   200  					Key:  "key",
   201  					Data: []byte(content),
   202  				},
   203  			},
   204  		},
   205  	}
   206  }
   207  
   208  func TestBucketAlertStore_GetSetDeleteFullState(t *testing.T) {
   209  	bucket := objstore.NewInMemBucket()
   210  	store := bucketclient.NewBucketAlertStore(bucket, nil, log.NewNopLogger())
   211  	ctx := context.Background()
   212  
   213  	state1 := makeTestFullState("one")
   214  	state2 := makeTestFullState("two")
   215  
   216  	// The storage is empty.
   217  	{
   218  		_, err := store.GetFullState(ctx, "user-1")
   219  		assert.Equal(t, alertspb.ErrNotFound, err)
   220  
   221  		_, err = store.GetFullState(ctx, "user-2")
   222  		assert.Equal(t, alertspb.ErrNotFound, err)
   223  
   224  		users, err := store.ListUsersWithFullState(ctx)
   225  		assert.NoError(t, err)
   226  		assert.ElementsMatch(t, []string{}, users)
   227  	}
   228  
   229  	// The storage contains users.
   230  	{
   231  		require.NoError(t, store.SetFullState(ctx, "user-1", state1))
   232  		require.NoError(t, store.SetFullState(ctx, "user-2", state2))
   233  
   234  		res, err := store.GetFullState(ctx, "user-1")
   235  		require.NoError(t, err)
   236  		assert.Equal(t, state1, res)
   237  
   238  		res, err = store.GetFullState(ctx, "user-2")
   239  		require.NoError(t, err)
   240  		assert.Equal(t, state2, res)
   241  
   242  		// Ensure the config is stored at the expected location. Without this check
   243  		// we have no guarantee that the objects are stored at the expected location.
   244  		exists, err := bucket.Exists(ctx, "alertmanager/user-1/fullstate")
   245  		require.NoError(t, err)
   246  		assert.True(t, exists)
   247  
   248  		exists, err = bucket.Exists(ctx, "alertmanager/user-2/fullstate")
   249  		require.NoError(t, err)
   250  		assert.True(t, exists)
   251  
   252  		users, err := store.ListUsersWithFullState(ctx)
   253  		assert.NoError(t, err)
   254  		assert.ElementsMatch(t, []string{"user-1", "user-2"}, users)
   255  	}
   256  
   257  	// The storage has had user-1 deleted.
   258  	{
   259  		require.NoError(t, store.DeleteFullState(ctx, "user-1"))
   260  
   261  		// Ensure the correct entry has been deleted.
   262  		_, err := store.GetFullState(ctx, "user-1")
   263  		assert.Equal(t, alertspb.ErrNotFound, err)
   264  
   265  		res, err := store.GetFullState(ctx, "user-2")
   266  		require.NoError(t, err)
   267  		assert.Equal(t, state2, res)
   268  
   269  		users, err := store.ListUsersWithFullState(ctx)
   270  		assert.NoError(t, err)
   271  		assert.ElementsMatch(t, []string{"user-2"}, users)
   272  
   273  		// Delete again (should be idempotent).
   274  		require.NoError(t, store.DeleteFullState(ctx, "user-1"))
   275  	}
   276  }