Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ nosetests.xml
node_modules

.ropeproject
.idea
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Features in short
- Gives you control over how much reads and writes you want to scale up and down with
- Dynamic DynamoDB has support for max and min limits so that you always knows how much money you spend at most and how much capacity you can be guaranteed
- Support for circuit breaker API call. If your service is experiencing disturbances, Dynamic DynamoDB will not scale down your DynamoDB tables
- Check to prevent dynamid-dynamodb from fighting with the auto-scale API and provisioning tables that are already autoscaling

Documentation
-------------
Expand Down Expand Up @@ -72,6 +73,7 @@ If you want to set up a separate IAM user for Dynamic DynamoDB, then you need to
* `dynamodb:ListTables`
* `dynamodb:UpdateTable`
* `sns:Publish` (used by the SNS notifications feature)
* `application-autoscaling:DescribeScalingPolicies` (used to prevent dynamic-dynamodb from changing provisioning on an autoscaled table)

An example policy could look like this:

Expand All @@ -84,6 +86,7 @@ An example policy could look like this:
"dynamodb:DescribeTable",
"dynamodb:ListTables",
"dynamodb:UpdateTable",
"application-autoscaling:DescribeScalingPolicies",
"cloudwatch:GetMetricStatistics"
],
"Resource": [
Expand Down
84 changes: 84 additions & 0 deletions dynamic_dynamodb/aws/dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from boto import dynamodb2
from boto.dynamodb2.table import Table
from boto.exception import DynamoDBResponseError, JSONResponseError
import boto3

from dynamic_dynamodb.log_handler import LOGGER as logger
from dynamic_dynamodb.config_handler import (
Expand Down Expand Up @@ -104,6 +105,38 @@ def get_gsi_status(table_name, gsi_name):
return gsi[u'IndexStatus']


def get_dynamodb_table_scaling_policy_enabled(table_name):
"""
Returns boolean value of if autoscale is enabled or not for a given table
:param table_name:
:return: Bool - True if enabled, false if not.
"""
scaling_policy = AUTO_SCALE_CLIENT.describe_scaling_policies(
ServiceNamespace='dynamodb',
ResourceId='table/{}'.format(table_name))
if scaling_policy['ScalingPolicies']:
return True
elif not scaling_policy['ScalingPolicies']:
return False


def get_dynamodb_gsi_scaling_policy_enabled(table_name, index):
"""
Returns boolean value of if autoscale is enabled or not for a given table and index
:param table_name: dynamodb table name
:param index: global secondary index of given table
:return: Bool - True if enabled, false if not.
"""
scaling_policy = AUTO_SCALE_CLIENT.describe_scaling_policies(
ServiceNamespace='dynamodb',
ResourceId='table/{}/index/{}'.format(table_name,
index))
if scaling_policy['ScalingPolicies']:
return True
elif not scaling_policy['ScalingPolicies']:
return False


def get_provisioned_gsi_read_units(table_name, gsi_name):
""" Returns the number of provisioned read units for the table

Expand Down Expand Up @@ -276,6 +309,11 @@ def update_table_provisioning(
:param retry_with_only_increase: Set to True to ensure only increases
"""
table = get_table(table_name)
# If table has autoscale enabled, don't touch it, log a warning.
if get_dynamodb_table_scaling_policy_enabled(table_name):
logger.warning('Table {} has autoscale enabled. Canceling provision change.'.format(table_name))
return

current_reads = int(get_provisioned_table_read_units(table_name))
current_writes = int(get_provisioned_table_write_units(table_name))

Expand Down Expand Up @@ -438,6 +476,12 @@ def update_gsi_provisioning(
:type retry_with_only_increase: bool
:param retry_with_only_increase: Set to True to ensure only increases
"""
# If the index has autoscale enabled, then we should skip it
if get_dynamodb_gsi_scaling_policy_enabled(table_name, gsi_name):
logger.warning('Table {} - GSI {} has autoscale enabled. Canceling provision change.'.format(table_name,
gsi_name))
return

current_reads = int(get_provisioned_gsi_read_units(table_name, gsi_name))
current_writes = int(get_provisioned_gsi_write_units(table_name, gsi_name))

Expand Down Expand Up @@ -616,6 +660,45 @@ def table_gsis(table_name):

return []

def __get_connection_autoscale(retries=3):
""" Ensure connection to AutoScale

:type retries: int
:param retries: Number of times to retry to connect to AutoScale
"""
connected = False
region = get_global_option('region')

while not connected:
if (get_global_option('aws_access_key_id') and
get_global_option('aws_secret_access_key')):
logger.debug(
'Authenticating to ApplicationAutoScaling using '
'credentials in configuration file')
connection = boto3.client('application-autoscaling',
region_name=region,
aws_access_key_id=get_global_option('aws_access_key_id'),
aws_secret_access_key=get_global_option('aws_secret_access_key'))
else:
logger.debug(
'Authenticating using boto3\'s authentication handler')
connection = boto3.client('application-autoscaling', region_name=region)

if not connection:
if retries == 0:
logger.error('Failed to connect to ApplicationAutoScaling. Giving up.')
raise
else:
logger.error(
'Failed to connect to ApplicationAutoScaling. Retrying in 5 seconds')
retries -= 1
time.sleep(5)
else:
connected = True
logger.debug('Connected to ApplicationAutoScaling in {0}'.format(region))

return connection


def __get_connection_dynamodb(retries=3):
""" Ensure connection to DynamoDB
Expand Down Expand Up @@ -723,3 +806,4 @@ def __is_table_maintenance_window(table_name, maintenance_windows):
return False

DYNAMODB_CONNECTION = __get_connection_dynamodb()
AUTO_SCALE_CLIENT = __get_connection_autoscale()