├── .gitignore ├── README.md ├── Vagrantfile ├── agent.py ├── aws_hosts.ini ├── group_vars └── all ├── locust.yml ├── remove_servers.yml ├── roles ├── common │ └── tasks │ │ └── main.yml ├── locust │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── agent.py │ │ ├── locustmaster.conf.j2 │ │ └── locustslave.conf.j2 └── python │ └── tasks │ └── main.yml └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | vagrant_ansible_inventory* 3 | aws_credentials 4 | ec2.py 5 | ec2.ini 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swarm for Locust.io 2 | 3 | Ansible AWS Provisioning for Locust.io Distributed load testing. There will be one Master VM and a number of slave VMs 4 | 5 | ## AWS 6 | - Depends on Ansible and boto 7 | $ sudo pip install ansible 8 | $ sudo pip install boto 9 | - Ships with ec2.py and ec2.ini from ansible. you probably want to check [this doc and update](http://docs.ansible.com/intro_dynamic_inventory.html) 10 | - Export credentials 11 | $ export AWS_ACCESS_KEY_ID= 12 | $ export AWS_SECRET_ACCESS_KEY= 13 | - Disable host key checking 14 | $ export ANSIBLE_HOST_KEY_CHECKING=False 15 | 16 | - At AWS, take note of your VPC id, subnet id, IP block assigned to this subnet and the key name you will be using. 17 | - Each region may have distinct AMI ids for Ubuntu 14.04 64 bits (trusty) 18 | - Configure group_vars/all with your data 19 | - slave_count is the number of slave VMs 20 | - test_host is the target host you will be testing (base host, not full URL) 21 | ssh_key_name: aws_devel 22 | aws_region: us-east-1 23 | ami_id: ami-9eaa1cf6 24 | instance_type: t2.micro 25 | vpc_id: vpc-ffffffff 26 | subnet_id: subnet-ffffffff 27 | slave_count: 3 28 | cidr_ip: 10.0.0.0/16 29 | test_host: "https://google.com" 30 | 31 | $ ansible-playbook -i aws_hosts.ini locust.yml --private-key ~/.ssh/aws_devel.pem 32 | - aws_devel.pem is your public key, the same referred in the file above 33 | - aws_hosts.ini file containing 34 | [local] 35 | localhost 36 | 37 | - use EC2 panel to remove servers or $ ansible-playbook -i ./ec2.py remove_servers.yml 38 | 39 | ## Testing agent 40 | 41 | The testing agent is agent.py, copied into templates/ dir of locust role. If you use *run.sh* it will do it for you 42 | 43 | ## Target host 44 | 45 | The target host is configured at locust.yml variable test_host 46 | 47 | ## Master 48 | 49 | Access the master VM using http://ip:8089 50 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | # Prepara os dados para todos os nodes do cluster 7 | nodes = ['192.168.33.100', '192.168.33.101', '192.168.33.102'] 8 | cluster_name = "Vagrant Cluster" 9 | 10 | servers = [] 11 | nodes.each_with_index do |node, idx| 12 | servers << { 13 | 'hostname' => 'node' + idx.to_s, 14 | 'ip' => node, 15 | 'seeds' => nodes.join(","), 16 | 'cluster_name' => cluster_name, 17 | } 18 | end 19 | 20 | # Para cada item da lista de servidores (servers) uma vm 21 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 22 | servers.each do |server| 23 | config.vm.define server['hostname'] do |cfg| 24 | cfg.vm.box = "ubuntu/trusty64" 25 | cfg.vm.host_name = server['name'] 26 | cfg.vm.network :private_network, ip: server['ip'] 27 | cfg.vm.provision "ansible" do |ansible| 28 | ansible.extra_vars = { 29 | cluster_name: server["cluster_name"], 30 | seeds: server["seeds"], 31 | listen_address: server['ip'], 32 | rpc_address: server['ip'] 33 | } 34 | ansible.verbose = 'vvvv' 35 | ansible.playbook = "cassandra.yml" 36 | end 37 | cfg.vm.provider "virtualbox" do |v| 38 | v.customize ["modifyvm", :id, "--memory", "2048"] 39 | end 40 | end 41 | end 42 | end 43 | 44 | -------------------------------------------------------------------------------- /agent.py: -------------------------------------------------------------------------------- 1 | from locust import HttpLocust, TaskSet, task 2 | # as per https://urllib3.readthedocs.org/en/latest/security.html#pyopenssl 3 | 4 | import urllib3.contrib.pyopenssl 5 | urllib3.contrib.pyopenssl.inject_into_urllib3() 6 | 7 | class WebsiteTasks(TaskSet): 8 | @task 9 | def index(self): 10 | self.client.get("/") 11 | 12 | @task 13 | def about(self): 14 | self.client.get("/about/") 15 | 16 | class WebsiteUser(HttpLocust): 17 | task_set = WebsiteTasks 18 | min_wait = 5000 19 | max_wait = 15000 20 | -------------------------------------------------------------------------------- /aws_hosts.ini: -------------------------------------------------------------------------------- 1 | [local] 2 | localhost 3 | -------------------------------------------------------------------------------- /group_vars/all: -------------------------------------------------------------------------------- 1 | ssh_key_name: aws_devel 2 | aws_region: us-east-1 3 | ami_id: ami-9eaa1cf6 4 | instance_type: t2.micro 5 | vpc_id: vpc-56e3b633 6 | subnet_id: subnet-d94a2bae 7 | slave_count: 3 8 | cidr_ip: 10.0.0.0/16 9 | test_host: "https://google.com" 10 | -------------------------------------------------------------------------------- /locust.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | 4 | tasks: 5 | - name: "Create security group" 6 | ec2_group: 7 | name: locust_group 8 | description: "Locust Security group" 9 | vpc_id: "{{vpc_id}}" 10 | region: "{{aws_region}}" 11 | rules: 12 | - proto: tcp 13 | type: ssh 14 | from_port: 22 15 | to_port: 22 16 | cidr_ip: 0.0.0.0/0 17 | 18 | - proto: tcp 19 | type: http 20 | from_port: 80 21 | to_port: 80 22 | cidr_ip: 0.0.0.0/0 23 | 24 | - proto: tcp 25 | type: http 26 | from_port: 8089 27 | to_port: 8089 28 | cidr_ip: 0.0.0.0/0 29 | 30 | - proto: tcp 31 | type: Custom TCP Rule 32 | from_port: 1024 33 | to_port: 65535 34 | cidr_ip: "{{ cidr_ip }}" 35 | 36 | rules_egress: 37 | - proto: all 38 | type: all 39 | cidr_ip: 0.0.0.0/0 40 | 41 | register: ec2_firewall 42 | 43 | - name: "Create Locust Master" 44 | local_action: ec2 key_name="{{ssh_key_name}}" 45 | count=1 46 | vpc_subnet_id="{{subnet_id}}" 47 | region="{{aws_region}}" 48 | group_id="{{ec2_firewall.group_id}}" 49 | instance_type="{{instance_type}}" 50 | image="{{ami_id}}" 51 | wait=yes 52 | assign_public_ip=yes 53 | register: locust_master 54 | 55 | - name: "Create Locust Slaves" 56 | local_action: ec2 key_name="{{ssh_key_name}}" 57 | count="{{ slave_count }}" 58 | vpc_subnet_id="{{subnet_id}}" 59 | region="{{aws_region}}" 60 | group_id="{{ec2_firewall.group_id}}" 61 | instance_type="{{instance_type}}" 62 | image="{{ami_id}}" 63 | wait=yes 64 | assign_public_ip=yes 65 | register: locust_slaves 66 | 67 | - name: "Add host to slaves" 68 | add_host: hostname={{ item.public_ip }} groupname=slaves 69 | with_items: locust_slaves.instances 70 | 71 | - name: "Add host to masters" 72 | add_host: hostname={{ item.public_ip }} groupname=masters 73 | with_items: locust_master.instances 74 | 75 | - name: "Add master invalid IP addr" 76 | add_host: hostname={{ item.private_ip}} groupname=private_ips 77 | with_items: locust_master.instances 78 | 79 | - name: "Wait for confirmation at port 22" 80 | wait_for: port=22 host="{{ item.public_ip }}" search_regex=OpenSSH delay=10 81 | with_items: locust_slaves.instances 82 | 83 | # locust.io slaves 84 | - hosts: slaves 85 | sudo: True 86 | user: ubuntu 87 | gather_facts: True 88 | vars: 89 | - master: "{{ groups['private_ips'] | join(',') }}" 90 | - master_node: False 91 | roles: 92 | - common 93 | - python 94 | - locust 95 | 96 | # locust.io master 97 | - hosts: masters 98 | sudo: True 99 | user: ubuntu 100 | gather_facts: True 101 | vars: 102 | - master: "{{ groups['private_ips'] | join(',') }}" 103 | - master_node: True 104 | roles: 105 | - common 106 | - python 107 | - locust 108 | 109 | -------------------------------------------------------------------------------- /remove_servers.yml: -------------------------------------------------------------------------------- 1 | - hosts: security_group_locust_group 2 | connection: local 3 | gather_facts: False 4 | tasks: 5 | - name: Remove a instância 6 | local_action: 7 | module: ec2 8 | state: 'absent' 9 | region: '{{aws_region}}' 10 | instance_ids: '{{ec2_id}}' 11 | 12 | - hosts: localhost 13 | connection: local 14 | gather_facts: False 15 | tasks: 16 | - name: Remove o security group locust_group 17 | local_action: 18 | description: "locust group" 19 | module: ec2_group 20 | name: security_group_locust_group 21 | region: "{{aws_region}}" 22 | state: 'absent' 23 | -------------------------------------------------------------------------------- /roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: "Update apt package cache" 2 | apt: update_cache=yes 3 | 4 | - name: "Install common packages debian/ubuntu" 5 | apt: pkg={{ item }} state=latest 6 | with_items: 7 | - build-essential 8 | - git 9 | - supervisor 10 | - libxml2-dev 11 | - cmake 12 | - pkg-config 13 | -------------------------------------------------------------------------------- /roles/locust/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: "Install locust" 2 | pip: name=locustio 3 | 4 | - name: "Create /opt/app" 5 | file: dest=/opt/app mode=755 state=directory 6 | 7 | - name: "Copy test agent to /opt/app" 8 | copy: src=agent.py dest=/opt/app/ 9 | 10 | - name: "Set locustslave entry on supervisord" 11 | template: src=locustslave.conf.j2 dest=/etc/supervisor/conf.d/locustslave.conf 12 | when: master_node == False 13 | 14 | - name: "Start application" 15 | supervisorctl: name=locustslave state=started 16 | when: master_node == False 17 | 18 | - name: "Set locustmaster entry on supervisord" 19 | template: src=locustmaster.conf.j2 dest=/etc/supervisor/conf.d/locustmaster.conf 20 | when: master_node == True 21 | 22 | - name: "Start application" 23 | supervisorctl: name=locustmaster state=started 24 | when: master_node == True 25 | 26 | - name: "Restart supervisord" 27 | service: name=supervisor state=restarted 28 | 29 | - name: "Print master address" 30 | debug: msg="{{ master }}" 31 | when: master_node == True 32 | 33 | -------------------------------------------------------------------------------- /roles/locust/templates/agent.py: -------------------------------------------------------------------------------- 1 | from locust import HttpLocust, TaskSet, task 2 | # as per https://urllib3.readthedocs.org/en/latest/security.html#pyopenssl 3 | 4 | import urllib3.contrib.pyopenssl 5 | urllib3.contrib.pyopenssl.inject_into_urllib3() 6 | 7 | class WebsiteTasks(TaskSet): 8 | @task 9 | def index(self): 10 | self.client.get("/") 11 | 12 | @task 13 | def about(self): 14 | self.client.get("/about/") 15 | 16 | class WebsiteUser(HttpLocust): 17 | task_set = WebsiteTasks 18 | min_wait = 5000 19 | max_wait = 15000 20 | -------------------------------------------------------------------------------- /roles/locust/templates/locustmaster.conf.j2: -------------------------------------------------------------------------------- 1 | [program:locustmaster] 2 | command=/usr/local/bin/locust -f /opt/app/agent.py --master --host={{ test_host }} 3 | redirect_stderr=true 4 | stdout_logfile=/var/log/locust_master.log 5 | stdout_logfile_maxbytes=100MB 6 | -------------------------------------------------------------------------------- /roles/locust/templates/locustslave.conf.j2: -------------------------------------------------------------------------------- 1 | [program:locustslave] 2 | command=/usr/local/bin/locust -f /opt/app/agent.py --slave --master-host={{ master }} --host={{ test_host }} 3 | redirect_stderr=true 4 | stdout_logfile=/var/log/locust_slave.log 5 | stdout_logfile_maxbytes=100MB 6 | -------------------------------------------------------------------------------- /roles/python/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: "Install python-related packages debian/ubuntu" 2 | apt: pkg={{ item }} state=latest 3 | with_items: 4 | - python 5 | - python-dev 6 | - python-setuptools 7 | 8 | - name: "Install pip" 9 | easy_install: name=pip 10 | 11 | - name: "Install virtualenv" 12 | pip: name=virtualenv 13 | 14 | - name: "Install pyzmq" 15 | pip: name={{ item }} 16 | with_items: 17 | - pyzmq 18 | - pyopenssl 19 | - ndg-httpsclient 20 | - pyasn1 21 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export ANSIBLE_HOST_KEY_CHECKING=False 4 | cp agent.py roles/locust/templates 5 | ansible-playbook -i aws_hosts.ini locust.yml --private-key ~/.ssh/aws_devel.pem 6 | 7 | --------------------------------------------------------------------------------