{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create a spot-priced AutoScaling group and a Bees With Machine Guns controller; execute the load test against the AutoScaling group and store the results in S3. Run /home/ec2-user/run-bees to execute load tests manually.",
"Parameters": {
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type": "String"
},
"BeesControllerInstanceType": {
"Description": "Type of EC2 instance to launch",
"Type": "String",
"Default": "c1.medium",
"AllowedValues": [
"t1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"c1.medium",
"c1.xlarge",
"cc1.4xlarge"
],
"ConstraintDescription": "Must be a valid EC2 instance type."
},
"TotalConnections": {
"Description": "Total connections per load tester",
"Type": "Number",
"Default": "200000"
},
"SpotPrice": {
"Description": "Spot price for application AutoScaling Group",
"Type": "Number",
"MinValue" : "0"
},
"ConcurrentConnections": {
"Description": "Number of concurrent requests per load tester",
"Type": "Number",
"Default": "1000"
},
"BeeCount": {
"Description": "Number of EC2 instances to launch as the load generators (bees)",
"Type": "Number",
"Default": "2"
},
"AppInstanceType": {
"Description": "Type of EC2 instant for application AutoScaling Group",
"Type": "String",
"Default": "c1.medium",
"AllowedValues": [
"t1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"c1.medium",
"c1.xlarge",
"cc1.4xlarge"
],
"ConstraintDescription": "must be a valid EC2 instance type."
},
"AppInstanceCountMin": {
"Description": "Minimum number of EC2 instances to launch for application AutoScaling Group",
"Type": "Number",
"Default": "2"
},
"AppInstanceCountMax": {
"Description": "Maximum number of EC2 instances to launch for application AutoScaling Group",
"Type": "Number",
"Default": "2"
},
"AppInstanceCountDesired": {
"Description": "Desired number of EC2 instances to launch for application AutoScaling Group",
"Type": "Number",
"Default": "2"
},
"RunTests": {
"Description": "Enter 'true' to run tests immediately. WARNING: CreateStack will not finish until test executes if this is set to 'true'",
"Type": "String",
"Default": "true",
"AllowedValues": [ "true", "false" ],
"ConstraintDescription": "Must be 'true' or 'false'."
},
"SSHLocation" : {
"Description" : "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "0.0.0.0/0",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Mappings": {
"AWSRegionPlatform2AMI": {
"us-east-1": {
"amzn": "ami-e565ba8c",
"bee" : "ami-e661c18f"
},
"us-west-1": {
"amzn": "ami-e78cd4a2",
"bee" : "ami-93b5efd6"
},
"eu-west-1": {
"amzn": "ami-f9231b8d",
"bee" : "ami-67212413"
},
"ap-southeast-1": {
"amzn": "ami-be3374ec",
"bee" : "ami-38bef86a"
},
"ap-northeast-1": {
"amzn": "ami-e47acbe5",
"bee" : "ami-16ac1f17"
},
"us-west-2": {
"amzn": "ami-3ac64a0a",
"bee" : "ami-bc05898c"
},
"sa-east-1": {
"amzn": "ami-a6855bbb",
"bee" : "ami-5a12cc47"
}
}
},
"Resources": {
"CfnUser": {
"Type": "AWS::IAM::User",
"Properties": {
"Path": "/",
"Policies": [
{
"PolicyName": "root",
"PolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Action": "cloudformation:DescribeStackResource",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "elasticloadbalancing:DescribeInstanceHealth",
"Resource": "*"
},
{
"Effect" : "Allow",
"Action" : "ec2:*",
"Resource" : "*"
}
]
}
}
]
}
},
"CfnKeys": {
"Type": "AWS::IAM::AccessKey",
"Properties": {
"UserName": {
"Ref": "CfnUser"
}
}
},
"ResultBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"AccessControl": "Private"
},
"DeletionPolicy" : "Retain"
},
"BucketPolicy" : {
"Type" : "AWS::S3::BucketPolicy",
"Properties" : {
"PolicyDocument": {
"Version" : "2008-10-17",
"Id" : "MyPolicy",
"Statement" : [{
"Sid" : "AllAccess",
"Action" : ["s3:*"],
"Effect" : "Allow",
"Resource" : { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ResultBucket" }, "/*"]]},
"Principal" : { "AWS": {"Fn::GetAtt" : ["CfnUser", "Arn"]} }
}]
},
"Bucket" : { "Ref" : "ResultBucket" }
}
},
"BeeController": {
"Type": "AWS::EC2::Instance",
"DependsOn": "AppGroup",
"Metadata": {
"AWS::CloudFormation::Init": {
"config": {
"packages": {
"yum": {
"gcc": [],
"gcc-c++": [],
"make": [],
"openssl-devel": [],
"httpd": [],
"python-paramiko": [],
"gmp-devel" : [],
"python26-devel" : []
},
"python" : {
"beeswithmachineguns" : []
}
},
"files": {
"/home/ec2-user/create-keypair" : {
"content" : {
"Fn::Join" : ["", ["#!/usr/bin/python\n",
"import string\n",
"import random\n",
"import boto.ec2\n",
"kp_name = ''.join(random.choice(string.letters) for i in xrange(16))\n",
"ec2 = boto.ec2.connect_to_region('", {"Ref" : "AWS::Region" }, "')\n",
"keypair = ec2.create_key_pair(kp_name)\n",
"keypair.save('/home/ec2-user/.ssh/')\n",
"with file('/home/ec2-user/bees_keypair.txt', 'w') as f:\n",
" f.write(kp_name)\n",
"print 'Created keypair: %s' % kp_name\n"]]
},
"mode" : "000750",
"owner" : "ec2-user",
"group" : "ec2-user"
},
"/home/ec2-user/delete-keypair" : {
"content" : {
"Fn::Join" : ["", ["#!/usr/bin/python\n",
"import string\n",
"import random\n",
"import boto.ec2\n",
"import os\n",
"import sys\n",
"if not os.path.exists('/home/ec2-user/bees_keypair.txt'):\n",
" print >> sys.stderr, 'bees_keypair.txt does not exist'\n",
" sys.exit(-1)\n",
"with file('/home/ec2-user/bees_keypair.txt', 'r') as f:\n",
" kp_name = f.read().strip()\n",
"ec2 = boto.ec2.connect_to_region('", {"Ref" : "AWS::Region" }, "')\n",
"ec2.delete_key_pair(kp_name)\n",
"os.remove('/home/ec2-user/bees_keypair.txt')\n",
"os.remove('/home/ec2-user/.ssh/%s.pem' % kp_name)\n",
"print 'Deleted keypair: %s' % kp_name\n"]]
},
"mode" : "000750",
"owner" : "ec2-user",
"group" : "ec2-user"
},
"/home/ec2-user/create-swarm": {
"content": {
"Fn::Join": [ "", ["#!/bin/bash\n",
"/usr/bin/bees up -k `cat /home/ec2-user/bees_keypair.txt` -s ", { "Ref": "BeeCount" },
" -z ", { "Fn::Select" : [ "1", { "Fn::GetAZs" : "" }] },
" -g ", { "Ref" : "BeeSecurityGroup" },
" --instance ", { "Fn::FindInMap": [ "AWSRegionPlatform2AMI", { "Ref": "AWS::Region" }, "bee"]},
" --login ec2-user\n"]]
},
"mode": "000755",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/start-swarm": {
"content": {
"Fn::Join": [ "", ["#!/bin/bash\n",
"/usr/bin/bees attack --url http://", { "Fn::GetAtt": [ "ElasticLoadBalancer", "DNSName" ] }, "/",
" -n ", { "Ref": "TotalConnections" },
" --concurrent ", { "Ref": "ConcurrentConnections" }]]
},
"mode": "000755",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/kill-swarm": {
"content": {
"Fn::Join": ["", ["#!/bin/bash\n",
"/usr/bin/bees down\n"]]
},
"mode": "000755",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/.boto": {
"content": {
"Fn::Join": ["", [ "[Credentials]\n",
"aws_access_key_id = ", { "Ref": "CfnKeys" }, "\n",
"aws_secret_access_key = ", { "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] }, "\n",
"[Boto]\n",
"ec2_region_name = ", { "Ref" : "AWS::Region" }, "\n",
"ec2_region_endpoint = ec2.", { "Ref" : "AWS::Region" }, ".amazonaws.com\n",
"elb_region_name = ", { "Ref" : "AWS::Region" }, "\n",
"elb_region_endpoint = elasticloadbalancing.", { "Ref" : "AWS::Region" }, ".amazonaws.com\n" ]]
},
"mode": "000600",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/run-bees": {
"content": {
"Fn::Join": ["", [ "#!/bin/bash\n\n",
"/home/ec2-user/wait-for-elb\n",
"if [ $? -eq 0 ]\n",
"then\n",
" mkdir /home/ec2-user/swarm-results\n",
" /home/ec2-user/create-keypair > /home/ec2-user/swarm-results/create-keypair.log 2>&1\n",
" bash /home/ec2-user/create-swarm > /home/ec2-user/swarm-results/create-swarm.log 2>&1\n",
" sleep 45 # Allow EC2 instances to fully come up\n",
" bash /home/ec2-user/start-swarm > /home/ec2-user/swarm-results/start-swarm.log 2>&1\n",
" bash /home/ec2-user/kill-swarm > /home/ec2-user/swarm-results/kill-swarm.log 2>&1\n",
" /home/ec2-user/delete-keypair > /home/ec2-user/swarm-results/delete-keypair.log 2>&1\n",
" tar cvf /home/ec2-user/swarm-results.tar.gz /home/ec2-user/swarm-results/*\n",
" chown ec2-user:ec2-user -R /home/ec2-user/swarm-results\n",
" chown ec2-user:ec2-user /home/ec2-user/swarm-results.tar.gz\n",
" aws put ", { "Ref": "ResultBucket" }, "/swarm-results.tar.gz /home/ec2-user/swarm-results.tar.gz\n",
"else\n",
" exit 1\n",
"fi\n"]]
},
"mode": "000755",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/wait-for-elb" : {
"content" : {
"Fn::Join" : ["", ["#!/usr/bin/python\n",
"import boto.ec2.elb\n",
"import sys\n",
"import time\n",
"elb = boto.ec2.elb.ELBConnection()\n",
"for i in range(120):\n",
" if i > 0:\n",
" time.sleep(5)\n",
" health=elb.describe_instance_health('", { "Ref": "ElasticLoadBalancer" }, "')\n",
" healthy_instances = [i for i in health if i.state == 'InService']\n",
" if len(healthy_instances) == ", { "Ref": "AppInstanceCountDesired" }, ":\n",
" break\n",
"else:\n",
" print >> sys.stderr, 'Gave up waiting for ", { "Ref": "AppInstanceCountDesired" }, "instances.'\n",
" sys.exit(1)\n"]]
},
"mode" : "000750",
"owner" : "ec2-user",
"group" : "ec2-user"
},
"/home/ec2-user/tools/aws" : {
"source" : "https://raw.github.com/timkay/aws/master/aws",
"mode" : "000755",
"owner": "ec2-user",
"group": "ec2-user"
},
"/home/ec2-user/.awssecret" : {
"content" : { "Fn::Join" : ["", [{ "Ref": "CfnKeys" }, "\n",
{ "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] }]] },
"mode" : "000600",
"owner": "ec2-user",
"group": "ec2-user"
},
"/root/.awssecret" : {
"content" : { "Fn::Join" : ["", [{ "Ref": "CfnKeys" }, "\n",
{ "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] }]] },
"mode" : "000600",
"owner": "root",
"group": "root"
}
},
"commands" : {
"00install_aws" : {
"command" : ["perl", "/home/ec2-user/tools/aws", "--install"]
},
"01run_bees" : {
"command" : ["su", "ec2-user", "-c", "./run-bees"],
"cwd" : "/home/ec2-user",
"test" : ["test", "true", "=", { "Ref": "RunTests" }]
}
}
}
}
},
"Properties": {
"SecurityGroups": [ { "Ref": "ControllerSecurityGroup" } ],
"KeyName": { "Ref": "KeyName" },
"ImageId": { "Fn::FindInMap": [ "AWSRegionPlatform2AMI", { "Ref": "AWS::Region" }, "amzn"]},
"InstanceType": { "Ref": "BeesControllerInstanceType" },
"Tags": [ { "Key": "Name", "Value": "bees-controller" } ],
"UserData": {
"Fn::Base64": {
"Fn::Join": [ "", [ "#!/bin/bash\n",
"yum update -y aws-cfn-bootstrap\n",
"/opt/aws/bin/cfn-init -v -s ", { "Ref": "AWS::StackName" },
" -r BeeController --access-key ", { "Ref": "CfnKeys" },
" --secret-key ", { "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] },
" --region ", { "Ref": "AWS::Region" }, "\n",
"/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "ControllerHandle" }, "'\n"
]
]
}
}
}
},
"ElasticLoadBalancer": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
"AvailabilityZones": { "Fn::GetAZs" : "" } ,
"Listeners": [ { "LoadBalancerPort": "80",
"InstancePort": "80",
"Protocol": "HTTP",
"InstanceProtocol": "HTTP" } ],
"HealthCheck": {
"Target": "HTTP:80/",
"HealthyThreshold": "2",
"UnhealthyThreshold": "10",
"Interval": "30",
"Timeout": "5"
}
}
},
"AppGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AvailabilityZones": { "Fn::GetAZs" : "" },
"LaunchConfigurationName": { "Ref": "LaunchConfig" },
"MinSize": { "Ref": "AppInstanceCountMin" },
"MaxSize": { "Ref": "AppInstanceCountMax" },
"DesiredCapacity": { "Ref": "AppInstanceCountDesired" },
"LoadBalancerNames": [ { "Ref": "ElasticLoadBalancer" } ]
}
},
"LaunchConfig": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Metadata" : {
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"nginx" : []
}
},
"services" : {
"sysvinit" : {
"nginx" : {
"enabled" : "true",
"ensureRunning" : "true",
"packages" : {
"yum" : ["nginx"]
}
}
}
}
}
}
},
"Properties": {
"SpotPrice" : { "Ref" : "SpotPrice" },
"ImageId": { "Fn::FindInMap": [ "AWSRegionPlatform2AMI", { "Ref": "AWS::Region" }, "amzn"]},
"UserData": {
"Fn::Base64": {
"Fn::Join": ["", [ "#!/bin/bash\n",
"yum update -y aws-cfn-bootstrap\n",
"/opt/aws/bin/cfn-init -v -s ", { "Ref": "AWS::StackName" },
" -r LaunchConfig --access-key ", { "Ref": "CfnKeys" },
" --secret-key ", { "Fn::GetAtt": ["CfnKeys", "SecretAccessKey"] },
" --region ", { "Ref": "AWS::Region" }, "\n"]]}
},
"SecurityGroups": [ { "Ref": "AppSecurityGroup" } ],
"InstanceType": { "Ref": "AppInstanceType" },
"KeyName": { "Ref": "KeyName" }
}
},
"ControllerSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable SSH access",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": { "Ref" : "SSHLocation"}
}
]
}
},
"BeeSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable SSH access and HTTP access on the inbound port",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"SourceSecurityGroupName" : { "Ref" : "ControllerSecurityGroup" }
}
]
}
},
"AppSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable tcp access on the inbound port for ELB and SSH from outside",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "80",
"SourceSecurityGroupOwnerId" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.OwnerAlias"]},
"SourceSecurityGroupName" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.GroupName"]}
},
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": { "Ref" : "SSHLocation"}
}]
}
},
"ControllerHandle" : {
"Type" : "AWS::CloudFormation::WaitConditionHandle"
},
"ControllerCondition" : {
"Type" : "AWS::CloudFormation::WaitCondition",
"DependsOn" : "BeeController",
"Properties" : {
"Handle" : { "Ref" : "ControllerHandle" },
"Timeout" : "900"
}
}
},
"Outputs": {
"WebsiteURL": {
"Description": "URL of website under test",
"Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["ElasticLoadBalancer", "DNSName"] }]]}
},
"BeeControllerAddress": {
"Description": "Public address of the bees controller",
"Value": { "Fn::GetAtt": [ "BeeController", "PublicDnsName" ] }
},
"TestResultsURL": {
"Value": { "Fn::Join": [ "", ["https://", { "Fn::GetAtt": [ "ResultBucket", "DomainName" ] }, "/swarm-results.tar.gz" ]]},
"Description": "URL of Results file"
}
}
}