github.com/sequix/cortex@v1.1.6/pkg/chunk/aws/dynamodb_table_client_test.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/request"
    11  	"github.com/aws/aws-sdk-go/service/applicationautoscaling"
    12  	"github.com/aws/aws-sdk-go/service/applicationautoscaling/applicationautoscalingiface"
    13  	"github.com/prometheus/common/model"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/weaveworks/common/mtime"
    16  
    17  	"github.com/sequix/cortex/pkg/chunk"
    18  )
    19  
    20  const (
    21  	tablePrefix      = "cortex_"
    22  	chunkTablePrefix = "chunks_"
    23  	tablePeriod      = 7 * 24 * time.Hour
    24  	gracePeriod      = 15 * time.Minute
    25  	maxChunkAge      = 12 * time.Hour
    26  	inactiveWrite    = 1
    27  	inactiveRead     = 2
    28  	write            = 200
    29  	read             = 100
    30  )
    31  
    32  func fixtureWriteScale() chunk.AutoScalingConfig {
    33  	return chunk.AutoScalingConfig{
    34  		Enabled:     true,
    35  		MinCapacity: 100,
    36  		MaxCapacity: 250,
    37  		OutCooldown: 100,
    38  		InCooldown:  100,
    39  		TargetValue: 80.0,
    40  	}
    41  }
    42  
    43  func fixtureReadScale() chunk.AutoScalingConfig {
    44  	return chunk.AutoScalingConfig{
    45  		Enabled:     true,
    46  		MinCapacity: 1,
    47  		MaxCapacity: 2000,
    48  		OutCooldown: 100,
    49  		InCooldown:  100,
    50  		TargetValue: 80.0,
    51  	}
    52  }
    53  
    54  func fixturePeriodicTableConfig(prefix string) chunk.PeriodicTableConfig {
    55  	return chunk.PeriodicTableConfig{
    56  		Prefix: prefix,
    57  		Period: tablePeriod,
    58  	}
    59  }
    60  
    61  func fixtureProvisionConfig(inactLastN int64, writeScale, inactWriteScale chunk.AutoScalingConfig) chunk.ProvisionConfig {
    62  	return chunk.ProvisionConfig{
    63  		ProvisionedWriteThroughput: write,
    64  		ProvisionedReadThroughput:  read,
    65  		InactiveWriteThroughput:    inactiveWrite,
    66  		InactiveReadThroughput:     inactiveRead,
    67  		WriteScale:                 writeScale,
    68  		InactiveWriteScale:         inactWriteScale,
    69  		InactiveWriteScaleLastN:    inactLastN,
    70  	}
    71  }
    72  
    73  func fixtureReadProvisionConfig(readScale, inactReadScale chunk.AutoScalingConfig) chunk.ProvisionConfig {
    74  	return chunk.ProvisionConfig{
    75  		ProvisionedWriteThroughput: write,
    76  		ProvisionedReadThroughput:  read,
    77  		InactiveWriteThroughput:    inactiveWrite,
    78  		InactiveReadThroughput:     inactiveRead,
    79  		ReadScale:                  readScale,
    80  		InactiveReadScale:          inactReadScale,
    81  	}
    82  }
    83  
    84  func baseTable(name string, provisionedRead, provisionedWrite int64) []chunk.TableDesc {
    85  	return []chunk.TableDesc{
    86  		{
    87  			Name:             name,
    88  			ProvisionedRead:  provisionedRead,
    89  			ProvisionedWrite: provisionedWrite,
    90  		},
    91  	}
    92  }
    93  
    94  func staticTable(i int, indexRead, indexWrite, chunkRead, chunkWrite int64) []chunk.TableDesc {
    95  	return []chunk.TableDesc{
    96  		{
    97  			Name:             tablePrefix + fmt.Sprint(i),
    98  			ProvisionedRead:  indexRead,
    99  			ProvisionedWrite: indexWrite,
   100  		},
   101  		{
   102  			Name:             chunkTablePrefix + fmt.Sprint(i),
   103  			ProvisionedRead:  chunkRead,
   104  			ProvisionedWrite: chunkWrite,
   105  		},
   106  	}
   107  }
   108  
   109  func autoScaledTable(i int, provisionedRead, provisionedWrite int64, indexOutCooldown int64, chunkTarget float64) []chunk.TableDesc {
   110  	chunkASC, indexASC := fixtureWriteScale(), fixtureWriteScale()
   111  	indexASC.OutCooldown = indexOutCooldown
   112  	chunkASC.TargetValue = chunkTarget
   113  	return []chunk.TableDesc{
   114  		{
   115  			Name:             tablePrefix + fmt.Sprint(i),
   116  			ProvisionedRead:  provisionedRead,
   117  			ProvisionedWrite: provisionedWrite,
   118  			WriteScale:       indexASC,
   119  		},
   120  		{
   121  			Name:             chunkTablePrefix + fmt.Sprint(i),
   122  			ProvisionedRead:  provisionedRead,
   123  			ProvisionedWrite: provisionedWrite,
   124  			WriteScale:       chunkASC,
   125  		},
   126  	}
   127  }
   128  
   129  func test(t *testing.T, client dynamoTableClient, tableManager *chunk.TableManager, name string, tm time.Time, expected []chunk.TableDesc) {
   130  	t.Run(name, func(t *testing.T) {
   131  		ctx := context.Background()
   132  		mtime.NowForce(tm)
   133  		defer mtime.NowReset()
   134  		if err := tableManager.SyncTables(ctx); err != nil {
   135  			t.Fatal(err)
   136  		}
   137  		err := chunk.ExpectTables(ctx, client, expected)
   138  		require.NoError(t, err)
   139  	})
   140  }
   141  
   142  func TestTableManagerAutoScaling(t *testing.T) {
   143  	dynamoDB := newMockDynamoDB(0, 0)
   144  	applicationAutoScaling := newMockApplicationAutoScaling()
   145  	client := dynamoTableClient{
   146  		DynamoDB:  dynamoDB,
   147  		autoscale: &awsAutoscale{ApplicationAutoScaling: applicationAutoScaling},
   148  	}
   149  
   150  	cfg := chunk.SchemaConfig{
   151  		Configs: []chunk.PeriodConfig{
   152  			{
   153  				IndexType: "aws-dynamo",
   154  			},
   155  			{
   156  				IndexType:   "aws-dynamo",
   157  				From:        chunk.DayTime{Time: model.TimeFromUnix(0)},
   158  				IndexTables: fixturePeriodicTableConfig(tablePrefix),
   159  				ChunkTables: fixturePeriodicTableConfig(chunkTablePrefix),
   160  			}},
   161  	}
   162  	tbm := chunk.TableManagerConfig{
   163  		CreationGracePeriod: gracePeriod,
   164  		IndexTables:         fixtureProvisionConfig(0, fixtureWriteScale(), chunk.AutoScalingConfig{}),
   165  		ChunkTables:         fixtureProvisionConfig(0, fixtureWriteScale(), chunk.AutoScalingConfig{}),
   166  	}
   167  
   168  	// Check tables are created with autoscale
   169  	{
   170  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   171  		if err != nil {
   172  			t.Fatal(err)
   173  		}
   174  
   175  		test(t, client,
   176  			tableManager,
   177  			"Create tables",
   178  			time.Unix(0, 0).Add(maxChunkAge).Add(gracePeriod),
   179  			append(baseTable("", inactiveRead, inactiveWrite),
   180  				autoScaledTable(0, read, write, 100, 80)...),
   181  		)
   182  	}
   183  
   184  	// Check tables are updated with new settings
   185  	{
   186  		tbm.IndexTables.WriteScale.OutCooldown = 200
   187  		tbm.ChunkTables.WriteScale.TargetValue = 90.0
   188  
   189  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   190  		if err != nil {
   191  			t.Fatal(err)
   192  		}
   193  
   194  		test(t, client,
   195  			tableManager,
   196  			"Update tables with new settings",
   197  			time.Unix(0, 0).Add(maxChunkAge).Add(gracePeriod),
   198  			append(baseTable("", inactiveRead, inactiveWrite),
   199  				autoScaledTable(0, read, write, 200, 90)...),
   200  		)
   201  	}
   202  
   203  	// Check tables are degristered when autoscaling is disabled for inactive tables
   204  	{
   205  		tbm.IndexTables.WriteScale.OutCooldown = 200
   206  		tbm.ChunkTables.WriteScale.TargetValue = 90.0
   207  
   208  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   209  		if err != nil {
   210  			t.Fatal(err)
   211  		}
   212  
   213  		test(t, client,
   214  			tableManager,
   215  			"Update tables with new settings",
   216  			time.Unix(0, 0).Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   217  			append(append(baseTable("", inactiveRead, inactiveWrite),
   218  				staticTable(0, inactiveRead, inactiveWrite, inactiveRead, inactiveWrite)...),
   219  				autoScaledTable(1, read, write, 200, 90)...),
   220  		)
   221  	}
   222  
   223  	// Check tables are degristered when autoscaling is disabled entirely
   224  	{
   225  		tbm.IndexTables.WriteScale.Enabled = false
   226  		tbm.ChunkTables.WriteScale.Enabled = false
   227  
   228  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   229  		if err != nil {
   230  			t.Fatal(err)
   231  		}
   232  
   233  		test(t, client,
   234  			tableManager,
   235  			"Update tables with new settings",
   236  			time.Unix(0, 0).Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   237  			append(append(baseTable("", inactiveRead, inactiveWrite),
   238  				staticTable(0, inactiveRead, inactiveWrite, inactiveRead, inactiveWrite)...),
   239  				staticTable(1, read, write, read, write)...),
   240  		)
   241  	}
   242  }
   243  
   244  func TestTableManagerInactiveAutoScaling(t *testing.T) {
   245  	dynamoDB := newMockDynamoDB(0, 0)
   246  	applicationAutoScaling := newMockApplicationAutoScaling()
   247  	client := dynamoTableClient{
   248  		DynamoDB:  dynamoDB,
   249  		autoscale: &awsAutoscale{ApplicationAutoScaling: applicationAutoScaling},
   250  	}
   251  
   252  	cfg := chunk.SchemaConfig{
   253  		Configs: []chunk.PeriodConfig{
   254  			{
   255  				IndexType:   "aws-dynamo",
   256  				IndexTables: chunk.PeriodicTableConfig{},
   257  			},
   258  			{
   259  				IndexType:   "aws-dynamo",
   260  				IndexTables: fixturePeriodicTableConfig(tablePrefix),
   261  				ChunkTables: fixturePeriodicTableConfig(chunkTablePrefix),
   262  			},
   263  		},
   264  	}
   265  	tbm := chunk.TableManagerConfig{
   266  		CreationGracePeriod: gracePeriod,
   267  		IndexTables:         fixtureProvisionConfig(2, chunk.AutoScalingConfig{}, fixtureWriteScale()),
   268  		ChunkTables:         fixtureProvisionConfig(2, chunk.AutoScalingConfig{}, fixtureWriteScale()),
   269  	}
   270  
   271  	// Check legacy and latest tables do not autoscale with inactive autoscale enabled.
   272  	{
   273  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   274  		if err != nil {
   275  			t.Fatal(err)
   276  		}
   277  
   278  		test(t, client,
   279  			tableManager,
   280  			"Legacy and latest tables",
   281  			time.Unix(0, 0).Add(maxChunkAge).Add(gracePeriod),
   282  			append(baseTable("", inactiveRead, inactiveWrite),
   283  				staticTable(0, read, write, read, write)...),
   284  		)
   285  	}
   286  
   287  	// Check inactive tables are autoscaled even if there are less than the limit.
   288  	{
   289  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   290  		if err != nil {
   291  			t.Fatal(err)
   292  		}
   293  
   294  		test(t, client,
   295  			tableManager,
   296  			"1 week of inactive tables with latest",
   297  			time.Unix(0, 0).Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   298  			append(append(baseTable("", inactiveRead, inactiveWrite),
   299  				autoScaledTable(0, inactiveRead, inactiveWrite, 100, 80)...),
   300  				staticTable(1, read, write, read, write)...),
   301  		)
   302  	}
   303  
   304  	// Check inactive tables past the limit do not autoscale but the latest N do.
   305  	{
   306  		tableManager, err := chunk.NewTableManager(tbm, cfg, maxChunkAge, client, nil)
   307  		if err != nil {
   308  			t.Fatal(err)
   309  		}
   310  
   311  		test(t, client,
   312  			tableManager,
   313  			"3 weeks of inactive tables with latest",
   314  			time.Unix(0, 0).Add(tablePeriod*3).Add(maxChunkAge).Add(gracePeriod),
   315  			append(append(append(append(baseTable("", inactiveRead, inactiveWrite),
   316  				staticTable(0, inactiveRead, inactiveWrite, inactiveRead, inactiveWrite)...),
   317  				autoScaledTable(1, inactiveRead, inactiveWrite, 100, 80)...),
   318  				autoScaledTable(2, inactiveRead, inactiveWrite, 100, 80)...),
   319  				staticTable(3, read, write, read, write)...),
   320  		)
   321  	}
   322  }
   323  
   324  type mockApplicationAutoScalingClient struct {
   325  	applicationautoscalingiface.ApplicationAutoScalingAPI
   326  
   327  	scalableTargets map[string]mockScalableTarget
   328  	scalingPolicies map[string]mockScalingPolicy
   329  }
   330  
   331  type mockScalableTarget struct {
   332  	RoleARN     string
   333  	MinCapacity int64
   334  	MaxCapacity int64
   335  }
   336  
   337  type mockScalingPolicy struct {
   338  	ScaleInCooldown  int64
   339  	ScaleOutCooldown int64
   340  	TargetValue      float64
   341  }
   342  
   343  func newMockApplicationAutoScaling() *mockApplicationAutoScalingClient {
   344  	return &mockApplicationAutoScalingClient{
   345  		scalableTargets: map[string]mockScalableTarget{},
   346  		scalingPolicies: map[string]mockScalingPolicy{},
   347  	}
   348  }
   349  
   350  func (m *mockApplicationAutoScalingClient) RegisterScalableTarget(input *applicationautoscaling.RegisterScalableTargetInput) (*applicationautoscaling.RegisterScalableTargetOutput, error) {
   351  	m.scalableTargets[*input.ResourceId] = mockScalableTarget{
   352  		RoleARN:     *input.RoleARN,
   353  		MinCapacity: *input.MinCapacity,
   354  		MaxCapacity: *input.MaxCapacity,
   355  	}
   356  	return &applicationautoscaling.RegisterScalableTargetOutput{}, nil
   357  }
   358  
   359  func (m *mockApplicationAutoScalingClient) DeregisterScalableTarget(input *applicationautoscaling.DeregisterScalableTargetInput) (*applicationautoscaling.DeregisterScalableTargetOutput, error) {
   360  	delete(m.scalableTargets, *input.ResourceId)
   361  	return &applicationautoscaling.DeregisterScalableTargetOutput{}, nil
   362  }
   363  
   364  func (m *mockApplicationAutoScalingClient) DescribeScalableTargetsWithContext(ctx aws.Context, input *applicationautoscaling.DescribeScalableTargetsInput, options ...request.Option) (*applicationautoscaling.DescribeScalableTargetsOutput, error) {
   365  	scalableTarget, ok := m.scalableTargets[*input.ResourceIds[0]]
   366  	if !ok {
   367  		return &applicationautoscaling.DescribeScalableTargetsOutput{}, nil
   368  	}
   369  	return &applicationautoscaling.DescribeScalableTargetsOutput{
   370  		ScalableTargets: []*applicationautoscaling.ScalableTarget{
   371  			{
   372  				RoleARN:     aws.String(scalableTarget.RoleARN),
   373  				MinCapacity: aws.Int64(scalableTarget.MinCapacity),
   374  				MaxCapacity: aws.Int64(scalableTarget.MaxCapacity),
   375  			},
   376  		},
   377  	}, nil
   378  }
   379  
   380  func (m *mockApplicationAutoScalingClient) PutScalingPolicy(input *applicationautoscaling.PutScalingPolicyInput) (*applicationautoscaling.PutScalingPolicyOutput, error) {
   381  	m.scalingPolicies[*input.ResourceId] = mockScalingPolicy{
   382  		ScaleInCooldown:  *input.TargetTrackingScalingPolicyConfiguration.ScaleInCooldown,
   383  		ScaleOutCooldown: *input.TargetTrackingScalingPolicyConfiguration.ScaleOutCooldown,
   384  		TargetValue:      *input.TargetTrackingScalingPolicyConfiguration.TargetValue,
   385  	}
   386  	return &applicationautoscaling.PutScalingPolicyOutput{}, nil
   387  }
   388  
   389  func (m *mockApplicationAutoScalingClient) DeleteScalingPolicy(input *applicationautoscaling.DeleteScalingPolicyInput) (*applicationautoscaling.DeleteScalingPolicyOutput, error) {
   390  	delete(m.scalingPolicies, *input.ResourceId)
   391  	return &applicationautoscaling.DeleteScalingPolicyOutput{}, nil
   392  }
   393  
   394  func (m *mockApplicationAutoScalingClient) DescribeScalingPoliciesWithContext(ctx aws.Context, input *applicationautoscaling.DescribeScalingPoliciesInput, options ...request.Option) (*applicationautoscaling.DescribeScalingPoliciesOutput, error) {
   395  	scalingPolicy, ok := m.scalingPolicies[*input.ResourceId]
   396  	if !ok {
   397  		return &applicationautoscaling.DescribeScalingPoliciesOutput{}, nil
   398  	}
   399  	return &applicationautoscaling.DescribeScalingPoliciesOutput{
   400  		ScalingPolicies: []*applicationautoscaling.ScalingPolicy{
   401  			{
   402  				TargetTrackingScalingPolicyConfiguration: &applicationautoscaling.TargetTrackingScalingPolicyConfiguration{
   403  					ScaleInCooldown:  aws.Int64(scalingPolicy.ScaleInCooldown),
   404  					ScaleOutCooldown: aws.Int64(scalingPolicy.ScaleOutCooldown),
   405  					TargetValue:      aws.Float64(scalingPolicy.TargetValue),
   406  				},
   407  			},
   408  		},
   409  	}, nil
   410  }