Skip to content

Addition of function to calculate rally point instance in AWS Auto Scaling Groups #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 8, 2021
Merged
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
12 changes: 11 additions & 1 deletion Dockerfile.ubuntu16.04.bats
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ MAINTAINER Gruntwork <info@gruntwork.io>

# Install basic dependencies
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y vim python-pip jq sudo curl
apt-get install -y vim python-pip jq sudo curl libffi-dev python3-dev && \
update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \
update-alternatives --config python && \
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o /tmp/get-pip.py && \
python /tmp/get-pip.py

# Install Bats
RUN apt-get install -y software-properties-common && \
Expand All @@ -25,3 +29,9 @@ RUN apt-get install -y net-tools iptables

# Copy mock AWS CLI into the PATH
COPY ./.circleci/aws-local.sh /usr/local/bin/aws

# These have been added to resolve some encoding error issues with the tests. These were introduced during the upgrade to Python 3.6,
# which is known to have some sensitivity around locale issues. These variables should correct that, per examples like this SO thread:
# https://stackoverflow.com/questions/51026315/how-to-solve-unicodedecodeerror-in-python-3-6/51027262#51027262.
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
15 changes: 13 additions & 2 deletions Dockerfile.ubuntu18.04.bats
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MAINTAINER Gruntwork <info@gruntwork.io>

# Install basic dependencies
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y vim python-pip jq sudo curl
apt-get install -y vim python-pip jq sudo curl libffi-dev python3-dev

# Install Bats
RUN apt-get install -y software-properties-common && \
Expand All @@ -12,7 +12,12 @@ RUN apt-get install -y software-properties-common && \
apt-get install -y bats

# Install AWS CLI
RUN pip install awscli --upgrade --user
RUN apt install python3-distutils python3-apt -y && \
update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \
update-alternatives --config python && \
curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py && \
python /tmp/get-pip.py && \
pip install awscli --upgrade --user

# Install moto: https://github.com/spulec/moto
# Lock cfn-lint and pysistent to last known working versions
Expand All @@ -23,3 +28,9 @@ RUN apt-get install -y net-tools iptables

# Copy mock AWS CLI into the PATH
COPY ./.circleci/aws-local.sh /usr/local/bin/aws

# These have been added to resolve some encoding error issues with the tests. These were introduced during the upgrade to Python 3.6,
# which is known to have some sensitivity around locale issues. These variables should correct that, per examples like this SO thread:
# https://stackoverflow.com/questions/51026315/how-to-solve-unicodedecodeerror-in-python-3-6/51027262#51027262.
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
33 changes: 33 additions & 0 deletions modules/bash-commons/src/aws-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,36 @@ function aws_wrapper_get_hostname {
aws_get_instance_private_hostname
fi
}

# Calculates a "rally point" instance in an ASG and returns its hostname. This is a deterministic way for the instances in an ASG to all pick the same single instance to perform some action: e.g., this instance could become the leader in a cluster or run some initialization script that should only be run once for the entire ASG. Under the hood, this method picks the instance in the ASG with the earliest launch time; in the case of ties, the instance with the earliest instance ID (lexicographically) is returned. This method assumes jq is installed.
function aws_wrapper_get_asg_rally_point {
local -r asg_name="$1"
local -r aws_region="$2"
local -r use_public_hostname="$3"
local -r retries="${4:-60}"
local -r sleep_between_retries="${5:-5}"

log_info "Calculating rally point for ASG $asg_name in $aws_region"

local instances
log_info "Waiting for all instances to be available..."
instances=$(aws_wrapper_wait_for_instances_in_asg $asg_name $aws_region $retries $sleep_between_retries)
assert_not_empty_or_null "$instances" "Wait for instances in ASG $asg_name in $aws_region"

local rally_point
rally_point=$(echo "$instances" | jq -r '[.Reservations[].Instances[]] | sort_by(.LaunchTime, .InstanceId) | .[0]')
assert_not_empty_or_null "$rally_point" "Select rally point server in ASG $asg_name"

local hostname_field=".PrivateDnsName"
if [[ "$use_public_hostname" == "true" ]]; then
hostname_field=".PublicDnsName"
fi

log_info "Hostname field is $hostname_field"

local hostname
hostname=$(echo "$rally_point" | jq -r "$hostname_field")
assert_not_empty_or_null "$hostname" "Get hostname from field $hostname_field for rally point in $asg_name: $rally_point"

echo -n "$hostname"
}
285 changes: 285 additions & 0 deletions test/aws-wrapper.bats
Original file line number Diff line number Diff line change
Expand Up @@ -1311,4 +1311,289 @@ END_HEREDOC

assert_success
assert_equal "ip-10-251-50-12.ec2.internal" "$out"
}

function setup_rally_point_by_instance {
local -r asg_name="$1"
local -r aws_region="$2"
local -r size=3

local -r asg=$(cat <<END_HEREDOC
{
"AutoScalingGroups": [
{
"AutoScalingGroupARN": "arn:aws:autoscaling:us-west-2:123456789012:autoScalingGroup:930d940e-891e-4781-a11a-7b0acd480f03:autoScalingGroupName/$asg_name",
"DesiredCapacity": $size,
"AutoScalingGroupName": "$asg_name",
"MinSize": 0,
"MaxSize": 10,
"LaunchConfigurationName": "my-launch-config",
"CreatedTime": "2013-08-19T20:53:25.584Z",
"AvailabilityZones": [
"${aws_region}c"
]
}
]
}
END_HEREDOC
)

local -r instances=$(cat <<END_HEREDOC
{
"Reservations": [
{
"Instances": [
{
"LaunchTime": "2013-08-19T20:53:25.584Z",
"InstanceId": "i-1234567890abcdef0",
"PublicIpAddress": "55.66.77.88",
"PrivateIpAddress": "11.22.33.44",
"PrivateDnsName": "ip-10-251-50-12.ec2.internal",
"PublicDnsName": "ec2-203-0-113-25.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
}
]
},
{
"Instances": [
{
"LaunchTime": "2013-08-19T20:53:25.584Z",
"InstanceId": "i-1234567890abcdef1",
"PublicIpAddress": "55.66.77.881",
"PrivateIpAddress": "11.22.33.441",
"PrivateDnsName": "ip-10-251-50-121.ec2.internal",
"PublicDnsName": "ec2-203-0-113-252.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
},
{
"LaunchTime": "2013-08-19T20:53:25.584Z",
"InstanceId": "i-1234567890abcdef2",
"PublicIpAddress": "55.66.77.882",
"PrivateIpAddress": "11.22.33.442",
"PrivateDnsName": "ip-10-251-50-122.ec2.internal",
"PublicDnsName": "ec2-203-0-113-253.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
}
]
}
]
}
END_HEREDOC
)

load_aws_mock "" "$asg" "$instances"
}

@test "aws_wrapper_get_asg_rally_point by instance id, private" {
local -r asg_name="foo"
local -r aws_region="us-west-2"

setup_rally_point_by_instance $asg_name $aws_region

local out

out=$(aws_wrapper_get_asg_rally_point $asg_name $aws_region)
assert_success
assert_equal "$out" "ip-10-251-50-12.ec2.internal"
}

@test "aws_wrapper_get_asg_rally_point by instance id, public" {
local -r asg_name="foo"
local -r aws_region="us-west-2"

setup_rally_point_by_instance $asg_name $aws_region

local out

out=$(aws_wrapper_get_asg_rally_point $asg_name $aws_region true)
assert_success
assert_equal "$out" "ec2-203-0-113-25.compute-1.amazonaws.com"
}

function setup_rally_point_by_launch_time {
local -r asg_name="$1"
local -r aws_region="$2"
local -r size=3

local -r asg=$(cat <<END_HEREDOC
{
"AutoScalingGroups": [
{
"AutoScalingGroupARN": "arn:aws:autoscaling:us-west-2:123456789012:autoScalingGroup:930d940e-891e-4781-a11a-7b0acd480f03:autoScalingGroupName/$asg_name",
"DesiredCapacity": $size,
"AutoScalingGroupName": "$asg_name",
"MinSize": 0,
"MaxSize": 10,
"LaunchConfigurationName": "my-launch-config",
"CreatedTime": "2013-08-19T20:53:25.584Z",
"AvailabilityZones": [
"${aws_region}c"
]
}
]
}
END_HEREDOC
)

local -r instances=$(cat <<END_HEREDOC
{
"Reservations": [
{
"Instances": [
{
"LaunchTime": "2013-08-19T20:53:25.584Z",
"InstanceId": "i-1234567890abcdef0",
"PublicIpAddress": "55.66.77.88",
"PrivateIpAddress": "11.22.33.44",
"PrivateDnsName": "ip-10-251-50-12.ec2.internal",
"PublicDnsName": "ec2-203-0-113-25.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
}
]
},
{
"Instances": [
{
"LaunchTime": "2013-08-19T20:53:25.584Z",
"InstanceId": "i-1234567890abcdef1",
"PublicIpAddress": "55.66.77.881",
"PrivateIpAddress": "11.22.33.441",
"PrivateDnsName": "ip-10-251-50-121.ec2.internal",
"PublicDnsName": "ec2-203-0-113-252.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
},
{
"LaunchTime": "2013-08-19T20:53:24.584Z",
"InstanceId": "i-1234567890abcdef2",
"PublicIpAddress": "55.66.77.882",
"PrivateIpAddress": "11.22.33.442",
"PrivateDnsName": "ip-10-251-50-122.ec2.internal",
"PublicDnsName": "ec2-203-0-113-253.compute-1.amazonaws.com",
"Tags": [
{
"Value": "$asg_name",
"Key": "aws:autoscaling:groupName"
}
]
}
]
}
]
}
END_HEREDOC
)

load_aws_mock "" "$asg" "$instances"
}

@test "aws_wrapper_get_asg_rally_point by launch time, private" {
local -r asg_name="foo"
local -r aws_region="us-west-2"

setup_rally_point_by_launch_time $asg_name $aws_region

local out

out=$(aws_wrapper_get_asg_rally_point $asg_name $aws_region)
assert_success
assert_equal "$out" "ip-10-251-50-122.ec2.internal"
}

@test "aws_wrapper_get_asg_rally_point by launch time, public" {
local -r asg_name="foo"
local -r aws_region="us-west-2"

setup_rally_point_by_launch_time $asg_name $aws_region

local out

out=$(aws_wrapper_get_asg_rally_point $asg_name $aws_region true)
assert_success
assert_equal "$out" "ec2-203-0-113-253.compute-1.amazonaws.com"
}

function setup_rally_point_empty {
local -r asg_name="$1"
local -r aws_region="$2"
local -r size=3

local -r asg=$(cat <<END_HEREDOC
{
"AutoScalingGroups": [
{
"AutoScalingGroupARN": "arn:aws:autoscaling:us-west-2:123456789012:autoScalingGroup:930d940e-891e-4781-a11a-7b0acd480f03:autoScalingGroupName/$asg_name",
"DesiredCapacity": $size,
"AutoScalingGroupName": "$asg_name",
"MinSize": 0,
"MaxSize": 10,
"LaunchConfigurationName": "my-launch-config",
"CreatedTime": "2013-08-19T20:53:25.584Z",
"AvailabilityZones": [
"${aws_region}c"
]
}
]
}
END_HEREDOC
)

local -r instances=$(cat <<END_HEREDOC
{
"Reservations": []
}
END_HEREDOC
)

load_aws_mock "" "$asg" "$instances"
}

@test "aws_wrapper_get_asg_rally_point empty asg, private" {
local -r asg_name="foo"
local -r aws_region="us-west-2"
local -r use_public_hostname="false"
local -r retries=5
local -r sleep_between_retries=2

setup_rally_point_empty $asg_name $aws_region

run aws_wrapper_get_asg_rally_point $asg_name $aws_region $use_public_hostname $retries $sleep_between_retries
assert_failure
}

@test "aws_wrapper_get_asg_rally_point empty asg, public" {
local -r asg_name="foo"
local -r aws_region="us-west-2"
local -r use_public_hostname="true"
local -r retries=5
local -r sleep_between_retries=2

setup_rally_point_empty $asg_name $aws_region

run aws_wrapper_get_asg_rally_point $asg_name $aws_region $use_public_hostname $retries $sleep_between_retries
assert_failure
}