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

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/service/applicationautoscaling"
     9  	"github.com/aws/aws-sdk-go/service/applicationautoscaling/applicationautoscalingiface"
    10  	"github.com/go-kit/kit/log/level"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	"github.com/sequix/cortex/pkg/chunk"
    14  	"github.com/sequix/cortex/pkg/util"
    15  	"github.com/weaveworks/common/instrument"
    16  )
    17  
    18  const (
    19  	autoScalingPolicyNamePrefix = "DynamoScalingPolicy_cortex_"
    20  )
    21  
    22  var applicationAutoScalingRequestDuration = instrument.NewHistogramCollector(prometheus.NewHistogramVec(prometheus.HistogramOpts{
    23  	Namespace: "cortex",
    24  	Name:      "application_autoscaling_request_duration_seconds",
    25  	Help:      "Time spent doing ApplicationAutoScaling requests.",
    26  
    27  	// AWS latency seems to range from a few ms to a few sec. So use 8 buckets
    28  	// from 128us to 2s. TODO: Confirm that this is the case for ApplicationAutoScaling.
    29  	Buckets: prometheus.ExponentialBuckets(0.000128, 4, 8),
    30  }, []string{"operation", "status_code"}))
    31  
    32  func init() {
    33  	applicationAutoScalingRequestDuration.Register()
    34  }
    35  
    36  type awsAutoscale struct {
    37  	call                   callManager
    38  	ApplicationAutoScaling applicationautoscalingiface.ApplicationAutoScalingAPI
    39  }
    40  
    41  func newAWSAutoscale(cfg DynamoDBConfig, callManager callManager) (*awsAutoscale, error) {
    42  	session, err := awsSessionFromURL(cfg.ApplicationAutoScaling.URL)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	return &awsAutoscale{
    47  		call:                   callManager,
    48  		ApplicationAutoScaling: applicationautoscaling.New(session),
    49  	}, nil
    50  }
    51  
    52  func (a *awsAutoscale) PostCreateTable(ctx context.Context, desc chunk.TableDesc) error {
    53  	if desc.WriteScale.Enabled {
    54  		return a.enableAutoScaling(ctx, desc)
    55  	}
    56  	return nil
    57  }
    58  
    59  func (a *awsAutoscale) DescribeTable(ctx context.Context, desc *chunk.TableDesc) error {
    60  	err := a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
    61  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.DescribeScalableTargetsWithContext", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
    62  			out, err := a.ApplicationAutoScaling.DescribeScalableTargetsWithContext(ctx, &applicationautoscaling.DescribeScalableTargetsInput{
    63  				ResourceIds:       []*string{aws.String("table/" + desc.Name)},
    64  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
    65  				ServiceNamespace:  aws.String("dynamodb"),
    66  			})
    67  			if err != nil {
    68  				return err
    69  			}
    70  			switch l := len(out.ScalableTargets); l {
    71  			case 0:
    72  				return err
    73  			case 1:
    74  				desc.WriteScale.Enabled = true
    75  				if target := out.ScalableTargets[0]; target != nil {
    76  					if target.RoleARN != nil {
    77  						desc.WriteScale.RoleARN = *target.RoleARN
    78  					}
    79  					if target.MinCapacity != nil {
    80  						desc.WriteScale.MinCapacity = *target.MinCapacity
    81  					}
    82  					if target.MaxCapacity != nil {
    83  						desc.WriteScale.MaxCapacity = *target.MaxCapacity
    84  					}
    85  				}
    86  				return err
    87  			default:
    88  				return fmt.Errorf("more than one scalable target found for DynamoDB table")
    89  			}
    90  		})
    91  	})
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	err = a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
    97  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.DescribeScalingPoliciesWithContext", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
    98  			out, err := a.ApplicationAutoScaling.DescribeScalingPoliciesWithContext(ctx, &applicationautoscaling.DescribeScalingPoliciesInput{
    99  				PolicyNames:       []*string{aws.String(autoScalingPolicyNamePrefix + desc.Name)},
   100  				ResourceId:        aws.String("table/" + desc.Name),
   101  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
   102  				ServiceNamespace:  aws.String("dynamodb"),
   103  			})
   104  			if err != nil {
   105  				return err
   106  			}
   107  			switch l := len(out.ScalingPolicies); l {
   108  			case 0:
   109  				return err
   110  			case 1:
   111  				config := out.ScalingPolicies[0].TargetTrackingScalingPolicyConfiguration
   112  				if config != nil {
   113  					if config.ScaleInCooldown != nil {
   114  						desc.WriteScale.InCooldown = *config.ScaleInCooldown
   115  					}
   116  					if config.ScaleOutCooldown != nil {
   117  						desc.WriteScale.OutCooldown = *config.ScaleOutCooldown
   118  					}
   119  					if config.TargetValue != nil {
   120  						desc.WriteScale.TargetValue = *config.TargetValue
   121  					}
   122  				}
   123  				return err
   124  			default:
   125  				return fmt.Errorf("more than one scaling policy found for DynamoDB table")
   126  			}
   127  		})
   128  	})
   129  	return err
   130  }
   131  
   132  func (a *awsAutoscale) UpdateTable(ctx context.Context, current chunk.TableDesc, expected *chunk.TableDesc) error {
   133  	var err error
   134  	if !current.WriteScale.Enabled {
   135  		if expected.WriteScale.Enabled {
   136  			level.Info(util.Logger).Log("msg", "enabling autoscaling on table", "table")
   137  			err = a.enableAutoScaling(ctx, *expected)
   138  		}
   139  	} else {
   140  		if !expected.WriteScale.Enabled {
   141  			level.Info(util.Logger).Log("msg", "disabling autoscaling on table", "table")
   142  			err = a.disableAutoScaling(ctx, *expected)
   143  		} else if current.WriteScale != expected.WriteScale {
   144  			level.Info(util.Logger).Log("msg", "enabling autoscaling on table", "table")
   145  			err = a.enableAutoScaling(ctx, *expected)
   146  		}
   147  	}
   148  	return err
   149  }
   150  
   151  func (a *awsAutoscale) enableAutoScaling(ctx context.Context, desc chunk.TableDesc) error {
   152  	// Registers or updates a scalable target
   153  	if err := a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
   154  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.RegisterScalableTarget", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   155  			input := &applicationautoscaling.RegisterScalableTargetInput{
   156  				MinCapacity:       aws.Int64(desc.WriteScale.MinCapacity),
   157  				MaxCapacity:       aws.Int64(desc.WriteScale.MaxCapacity),
   158  				ResourceId:        aws.String("table/" + desc.Name),
   159  				RoleARN:           aws.String(desc.WriteScale.RoleARN),
   160  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
   161  				ServiceNamespace:  aws.String("dynamodb"),
   162  			}
   163  			_, err := a.ApplicationAutoScaling.RegisterScalableTarget(input)
   164  			if err != nil {
   165  				return err
   166  			}
   167  			return nil
   168  		})
   169  	}); err != nil {
   170  		return err
   171  	}
   172  
   173  	// Puts or updates a scaling policy
   174  	return a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
   175  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.PutScalingPolicy", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   176  			input := &applicationautoscaling.PutScalingPolicyInput{
   177  				PolicyName:        aws.String(autoScalingPolicyNamePrefix + desc.Name),
   178  				PolicyType:        aws.String("TargetTrackingScaling"),
   179  				ResourceId:        aws.String("table/" + desc.Name),
   180  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
   181  				ServiceNamespace:  aws.String("dynamodb"),
   182  				TargetTrackingScalingPolicyConfiguration: &applicationautoscaling.TargetTrackingScalingPolicyConfiguration{
   183  					PredefinedMetricSpecification: &applicationautoscaling.PredefinedMetricSpecification{
   184  						PredefinedMetricType: aws.String("DynamoDBWriteCapacityUtilization"),
   185  					},
   186  					ScaleInCooldown:  aws.Int64(desc.WriteScale.InCooldown),
   187  					ScaleOutCooldown: aws.Int64(desc.WriteScale.OutCooldown),
   188  					TargetValue:      aws.Float64(desc.WriteScale.TargetValue),
   189  				},
   190  			}
   191  			_, err := a.ApplicationAutoScaling.PutScalingPolicy(input)
   192  			return err
   193  		})
   194  	})
   195  }
   196  
   197  func (a *awsAutoscale) disableAutoScaling(ctx context.Context, desc chunk.TableDesc) error {
   198  	// Deregister scalable target
   199  	if err := a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
   200  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.DeregisterScalableTarget", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   201  			input := &applicationautoscaling.DeregisterScalableTargetInput{
   202  				ResourceId:        aws.String("table/" + desc.Name),
   203  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
   204  				ServiceNamespace:  aws.String("dynamodb"),
   205  			}
   206  			_, err := a.ApplicationAutoScaling.DeregisterScalableTarget(input)
   207  			return err
   208  		})
   209  	}); err != nil {
   210  		return err
   211  	}
   212  
   213  	// Delete scaling policy
   214  	return a.call.backoffAndRetry(ctx, func(ctx context.Context) error {
   215  		return instrument.CollectedRequest(ctx, "ApplicationAutoScaling.DeleteScalingPolicy", applicationAutoScalingRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
   216  			input := &applicationautoscaling.DeleteScalingPolicyInput{
   217  				PolicyName:        aws.String(autoScalingPolicyNamePrefix + desc.Name),
   218  				ResourceId:        aws.String("table/" + desc.Name),
   219  				ScalableDimension: aws.String("dynamodb:table:WriteCapacityUnits"),
   220  				ServiceNamespace:  aws.String("dynamodb"),
   221  			}
   222  			_, err := a.ApplicationAutoScaling.DeleteScalingPolicy(input)
   223  			return err
   224  		})
   225  	})
   226  }