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 }