Add heat siege workload scenario
Change-Id: I2621f4f75eac394951081270338bd63dc43b599e
This commit is contained in:
parent
32a7ef34f4
commit
30173ecbb1
219
rally-jobs/extra/workload/wordpress_heat_template.yaml
Normal file
219
rally-jobs/extra/workload/wordpress_heat_template.yaml
Normal file
@ -0,0 +1,219 @@
|
||||
heat_template_version: 2014-10-16
|
||||
|
||||
description: >
|
||||
Heat WordPress template to support F23, using only Heat OpenStack-native
|
||||
resource types, and without the requirement for heat-cfntools in the image.
|
||||
WordPress is web software you can use to create a beautiful website or blog.
|
||||
This template installs a single-instance WordPress deployment using a local
|
||||
MySQL database to store the data.
|
||||
|
||||
parameters:
|
||||
|
||||
wp_instances_count:
|
||||
type: number
|
||||
default: 1
|
||||
|
||||
timeout:
|
||||
type: number
|
||||
description: Timeout for WaitCondition, seconds
|
||||
default: 1000
|
||||
|
||||
router_id:
|
||||
type: string
|
||||
description: ID of the router
|
||||
default: b9135c24-d998-4e2f-b0aa-2b0a40c21ae5
|
||||
|
||||
network_id:
|
||||
type: string
|
||||
description: ID of the network to allocate floating IP from
|
||||
default: 4eabc459-0096-4479-b105-67ec0cff18cb
|
||||
|
||||
key_name:
|
||||
type: string
|
||||
description : Name of a KeyPair to enable SSH access to the instance
|
||||
default: nova-kp
|
||||
|
||||
wp_instance_type:
|
||||
type: string
|
||||
description: Instance type for WordPress server
|
||||
default: m1.small
|
||||
|
||||
wp_image:
|
||||
type: string
|
||||
description: >
|
||||
Name or ID of the image to use for the WordPress server.
|
||||
Recommended value is fedora-23.x86_64;
|
||||
http://cloud.fedoraproject.org/fedora-23.x86_64.qcow2.
|
||||
default: fedora-23.x86_64
|
||||
|
||||
image:
|
||||
type: string
|
||||
description: >
|
||||
Name or ID of the image to use for the gate-node.
|
||||
default: fedora-23.x86_64
|
||||
|
||||
instance_type:
|
||||
type: string
|
||||
description: Instance type for gate-node.
|
||||
default: m1.small
|
||||
|
||||
|
||||
db_name:
|
||||
type: string
|
||||
description: WordPress database name
|
||||
default: wordpress
|
||||
constraints:
|
||||
- length: { min: 1, max: 64 }
|
||||
description: db_name must be between 1 and 64 characters
|
||||
- allowed_pattern: '[a-zA-Z][a-zA-Z0-9]*'
|
||||
description: >
|
||||
db_name must begin with a letter and contain only alphanumeric
|
||||
characters
|
||||
db_username:
|
||||
type: string
|
||||
description: The WordPress database admin account username
|
||||
default: admin
|
||||
hidden: true
|
||||
constraints:
|
||||
- length: { min: 1, max: 16 }
|
||||
description: db_username must be between 1 and 16 characters
|
||||
- allowed_pattern: '[a-zA-Z][a-zA-Z0-9]*'
|
||||
description: >
|
||||
db_username must begin with a letter and contain only alphanumeric
|
||||
characters
|
||||
db_password:
|
||||
type: string
|
||||
description: The WordPress database admin account password
|
||||
default: admin
|
||||
hidden: true
|
||||
constraints:
|
||||
- length: { min: 1, max: 41 }
|
||||
description: db_password must be between 1 and 41 characters
|
||||
- allowed_pattern: '[a-zA-Z0-9]*'
|
||||
description: db_password must contain only alphanumeric characters
|
||||
db_root_password:
|
||||
type: string
|
||||
description: Root password for MySQL
|
||||
default: admin
|
||||
hidden: true
|
||||
constraints:
|
||||
- length: { min: 1, max: 41 }
|
||||
description: db_root_password must be between 1 and 41 characters
|
||||
- allowed_pattern: '[a-zA-Z0-9]*'
|
||||
description: db_root_password must contain only alphanumeric characters
|
||||
|
||||
resources:
|
||||
wordpress_instances:
|
||||
type: OS::Heat::ResourceGroup
|
||||
properties:
|
||||
count: {get_param: wp_instances_count}
|
||||
resource_def:
|
||||
type: wp-instances.yaml
|
||||
properties:
|
||||
name: wp_%index%
|
||||
image: { get_param: wp_image }
|
||||
flavor: { get_param: wp_instance_type }
|
||||
key_name: { get_param: key_name }
|
||||
db_root_password: { get_param: db_root_password }
|
||||
db_name: { get_param: db_name }
|
||||
db_username: { get_param: db_username }
|
||||
db_password: { get_param: db_password }
|
||||
wc_notify: { get_attr: ['wait_handle', 'curl_cli'] }
|
||||
subnet: {get_resource: subnet}
|
||||
network: {get_resource: network}
|
||||
security_group: {get_resource: security_group}
|
||||
|
||||
gate_instance:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: { get_param: image }
|
||||
flavor: { get_param: instance_type }
|
||||
key_name: { get_param: key_name }
|
||||
networks:
|
||||
- port: {get_resource: port_gate}
|
||||
user_data_format: RAW
|
||||
user_data: |
|
||||
#cloud-config
|
||||
packages:
|
||||
- python
|
||||
- siege
|
||||
- httpd-tools
|
||||
|
||||
security_group:
|
||||
type: OS::Neutron::SecurityGroup
|
||||
properties:
|
||||
rules:
|
||||
- port_range_max: null
|
||||
port_range_min: null
|
||||
protocol: icmp
|
||||
remote_ip_prefix: 0.0.0.0/0
|
||||
- port_range_max: 80
|
||||
port_range_min: 80
|
||||
protocol: tcp
|
||||
remote_ip_prefix: 0.0.0.0/0
|
||||
- port_range_max: 443
|
||||
port_range_min: 443
|
||||
protocol: tcp
|
||||
remote_ip_prefix: 0.0.0.0/0
|
||||
- port_range_max: 22
|
||||
port_range_min: 22
|
||||
protocol: tcp
|
||||
remote_ip_prefix: 0.0.0.0/0
|
||||
|
||||
network:
|
||||
type: OS::Neutron::Net
|
||||
properties:
|
||||
name: wordpress-network
|
||||
|
||||
subnet:
|
||||
type: OS::Neutron::Subnet
|
||||
properties:
|
||||
cidr: 10.0.0.1/24
|
||||
dns_nameservers: [8.8.8.8]
|
||||
ip_version: 4
|
||||
network: {get_resource: network}
|
||||
|
||||
port_gate:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
fixed_ips:
|
||||
- subnet: {get_resource: subnet}
|
||||
network: {get_resource: network}
|
||||
replacement_policy: AUTO
|
||||
security_groups:
|
||||
- {get_resource: security_group}
|
||||
|
||||
floating_ip:
|
||||
type: OS::Neutron::FloatingIP
|
||||
properties:
|
||||
port_id: {get_resource: port_gate}
|
||||
floating_network: {get_param: network_id}
|
||||
|
||||
router_interface:
|
||||
type: OS::Neutron::RouterInterface
|
||||
properties:
|
||||
router_id: {get_param: router_id}
|
||||
subnet: {get_resource: subnet}
|
||||
|
||||
wait_condition:
|
||||
type: OS::Heat::WaitCondition
|
||||
properties:
|
||||
handle: {get_resource: wait_handle}
|
||||
count: {get_param: wp_instances_count}
|
||||
timeout: {get_param: timeout}
|
||||
|
||||
wait_handle:
|
||||
type: OS::Heat::WaitConditionHandle
|
||||
|
||||
outputs:
|
||||
curl_cli:
|
||||
value: { get_attr: ['wait_handle', 'curl_cli'] }
|
||||
|
||||
wp_nodes:
|
||||
value: { get_attr: ['wordpress_instances', 'attributes', 'ip'] }
|
||||
|
||||
gate_node:
|
||||
value: { get_attr: ['floating_ip', 'floating_ip_address'] }
|
||||
|
||||
net_name:
|
||||
value: { get_attr: ['network', 'name'] }
|
81
rally-jobs/extra/workload/wp-instances.yaml
Normal file
81
rally-jobs/extra/workload/wp-instances.yaml
Normal file
@ -0,0 +1,81 @@
|
||||
heat_template_version: 2014-10-16
|
||||
|
||||
parameters:
|
||||
name: { type: string }
|
||||
wc_notify: { type: string }
|
||||
subnet: { type: string }
|
||||
network: { type: string }
|
||||
security_group: { type: string }
|
||||
key_name: { type: string }
|
||||
flavor: { type: string }
|
||||
image: { type: string }
|
||||
db_name: { type: string }
|
||||
db_username: { type: string }
|
||||
db_password: { type: string }
|
||||
db_root_password: { type: string }
|
||||
|
||||
resources:
|
||||
wordpress_instance:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
name: { get_param: name }
|
||||
image: { get_param: image }
|
||||
flavor: { get_param: flavor }
|
||||
key_name: { get_param: key_name }
|
||||
networks:
|
||||
- port: {get_resource: port}
|
||||
user_data_format: RAW
|
||||
user_data:
|
||||
str_replace:
|
||||
template: |
|
||||
#!/bin/bash -v
|
||||
sudo yum -y install mariadb mariadb-server httpd wordpress curl
|
||||
sudo touch /var/log/mariadb/mariadb.log
|
||||
sudo chown mysql.mysql /var/log/mariadb/mariadb.log
|
||||
sudo systemctl start mariadb.service
|
||||
# Setup MySQL root password and create a user
|
||||
sudo mysqladmin -u root password db_rootpassword
|
||||
cat << EOF | mysql -u root --password=db_rootpassword
|
||||
CREATE DATABASE db_name;
|
||||
GRANT ALL PRIVILEGES ON db_name.* TO "db_user"@"localhost"
|
||||
IDENTIFIED BY "db_password";
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT
|
||||
EOF
|
||||
sudo sed -i "/Deny from All/d" /etc/httpd/conf.d/wordpress.conf
|
||||
sudo sed -i "s/Require local/Require all granted/" /etc/httpd/conf.d/wordpress.conf
|
||||
sudo sed -i s/database_name_here/db_name/ /etc/wordpress/wp-config.php
|
||||
sudo sed -i s/username_here/db_user/ /etc/wordpress/wp-config.php
|
||||
sudo sed -i s/password_here/db_password/ /etc/wordpress/wp-config.php
|
||||
sudo systemctl start httpd.service
|
||||
IP=$(ip r get 8.8.8.8 | grep src | awk '{print $7}')
|
||||
curl --data 'user_name=admin&password=123&password2=123&admin_email=asd@asd.com' http://$IP/wordpress/wp-admin/install.php?step=2
|
||||
mkfifo /tmp/data
|
||||
(for i in $(seq 1000); do
|
||||
echo -n "1,$i,$i,page,"
|
||||
head -c 100000 /dev/urandom | base64 -w 0
|
||||
echo
|
||||
done
|
||||
) > /tmp/data &
|
||||
mysql -u root --password=db_rootpassword wordpress -e 'LOAD DATA LOCAL INFILE "/tmp/data" INTO TABLE wp_posts FIELDS TERMINATED BY "," (post_author,post_title,post_name,post_type,post_content);'
|
||||
wc_notify --data-binary '{"status": "SUCCESS"}'
|
||||
params:
|
||||
db_rootpassword: { get_param: db_root_password }
|
||||
db_name: { get_param: db_name }
|
||||
db_user: { get_param: db_username }
|
||||
db_password: { get_param: db_password }
|
||||
wc_notify: { get_param: wc_notify }
|
||||
|
||||
port:
|
||||
type: OS::Neutron::Port
|
||||
properties:
|
||||
fixed_ips:
|
||||
- subnet: {get_param: subnet}
|
||||
network: {get_param: network}
|
||||
replacement_policy: AUTO
|
||||
security_groups:
|
||||
- {get_param: security_group}
|
||||
|
||||
outputs:
|
||||
ip:
|
||||
value: { get_attr: ['wordpress_instance', 'networks'] }
|
@ -826,3 +826,38 @@
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
|
||||
VMTasks.runcommand_heat:
|
||||
-
|
||||
args:
|
||||
workload:
|
||||
resource: ["rally.plugins.workload", "siege.py"]
|
||||
username: "fedora"
|
||||
template: /home/rally/.rally/extra/workload/wordpress_heat_template.yaml
|
||||
files:
|
||||
wp-instances.yaml: /home/rally/.rally/extra/workload/wp-instances.yaml
|
||||
parameters:
|
||||
wp_instances_count: 2
|
||||
wp_instance_type: gig
|
||||
instance_type: gig
|
||||
wp_image: fedora
|
||||
image: fedora
|
||||
network_id: 9d477754-e9ba-4560-9b2b-9ce9d36638ce
|
||||
router_id: c497caa1-9d73-402b-bcd1-cc269e9af29e
|
||||
context:
|
||||
users:
|
||||
tenants: 1
|
||||
users_per_tenant: 1
|
||||
flavors:
|
||||
- name: gig
|
||||
ram: 1024
|
||||
disk: 4
|
||||
vcpus: 1
|
||||
runner:
|
||||
concurrency: 1
|
||||
timeout: 3000
|
||||
times: 1
|
||||
type: constant
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
|
@ -14,12 +14,16 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import pkgutil
|
||||
|
||||
from rally.common import logging
|
||||
from rally.common import sshutils
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally.plugins.openstack import scenario
|
||||
from rally.plugins.openstack.scenarios.vm import utils as vm_utils
|
||||
from rally.plugins.openstack.services import heat
|
||||
from rally.task import atomic
|
||||
from rally.task import types
|
||||
from rally.task import validation
|
||||
|
||||
@ -204,3 +208,66 @@ class VMTasks(vm_utils.VMScenario):
|
||||
|
||||
return self.boot_runcommand_delete(
|
||||
image=self.context["tenant"]["custom_image"]["id"], **kwargs)
|
||||
|
||||
@scenario.configure(context={"cleanup": ["nova", "heat"],
|
||||
"keypair": {}, "network": {}})
|
||||
def runcommand_heat(self, workload, template, files, parameters):
|
||||
"""Run workload on stack deployed by heat.
|
||||
|
||||
Workload can be either file or resource:
|
||||
{"file": "/path/to/file.sh"}
|
||||
{"resource": ["package.module", "workload.py"]}
|
||||
Also it should contain "username" key.
|
||||
Given file will be uploaded to `gate_node` and started. This script
|
||||
should print `key` `value` pairs separated by colon. These pairs will
|
||||
be presented in results.
|
||||
|
||||
Gate node should be accessible via ssh with keypair `key_name`, so
|
||||
heat template should accept parameter `key_name`.
|
||||
|
||||
:param workload: workload to run
|
||||
:param template: path to heat template file
|
||||
:param files: additional template files
|
||||
:param parameters: parameters for heat template
|
||||
"""
|
||||
keypair = self.context["user"]["keypair"]
|
||||
parameters["key_name"] = keypair["name"]
|
||||
network = self.context["tenant"]["networks"][0]
|
||||
parameters["router_id"] = network["router_id"]
|
||||
self.stack = heat.main.Stack(self, self.task,
|
||||
template, files=files,
|
||||
parameters=parameters)
|
||||
self.stack.create()
|
||||
for output in self.stack.stack.outputs:
|
||||
if output["output_key"] == "gate_node":
|
||||
ip = output["output_value"]
|
||||
break
|
||||
ssh = sshutils.SSH(workload["username"], ip, pkey=keypair["private"])
|
||||
ssh.wait()
|
||||
script = workload.get("resource")
|
||||
if script:
|
||||
script = pkgutil.get_data(*script)
|
||||
else:
|
||||
script = open(workload["file"]).read()
|
||||
ssh.execute("cat > /tmp/.rally-workload", stdin=script)
|
||||
ssh.execute("chmod +x /tmp/.rally-workload")
|
||||
with atomic.ActionTimer(self, "runcommand_heat.workload"):
|
||||
status, out, err = ssh.execute(
|
||||
"/tmp/.rally-workload",
|
||||
stdin=json.dumps(self.stack.stack.outputs))
|
||||
rows = []
|
||||
for line in out.splitlines():
|
||||
row = line.split(":")
|
||||
if len(row) != 2:
|
||||
raise exceptions.ScriptError("Invalid data '%s'" % line)
|
||||
rows.append(row)
|
||||
if not rows:
|
||||
raise exceptions.ScriptError("No data returned")
|
||||
self.add_output(
|
||||
complete={"title": "Workload summary",
|
||||
"description": "Data generated by workload",
|
||||
"chart_plugin": "Table",
|
||||
"data": {
|
||||
"cols": ["key", "value"],
|
||||
"rows": rows}}
|
||||
)
|
||||
|
0
rally/plugins/openstack/services/__init__.py
Normal file
0
rally/plugins/openstack/services/__init__.py
Normal file
0
rally/plugins/openstack/services/heat/__init__.py
Normal file
0
rally/plugins/openstack/services/heat/__init__.py
Normal file
77
rally/plugins/openstack/services/heat/main.py
Normal file
77
rally/plugins/openstack/services/heat/main.py
Normal file
@ -0,0 +1,77 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from rally.common import utils as common_utils
|
||||
from rally.task import atomic
|
||||
from rally.task import utils
|
||||
|
||||
|
||||
class Stack(common_utils.RandomNameGeneratorMixin):
|
||||
"""Represent heat stack.
|
||||
|
||||
Usage:
|
||||
>>> stack = Stack(scenario, task, "template.yaml", parameters={"nodes": 3})
|
||||
>>> run_benchmark(stack)
|
||||
>>> stack.update(nodes=4)
|
||||
>>> run_benchmark(stack)
|
||||
"""
|
||||
|
||||
def __init__(self, scenario, task, template, files, parameters=None):
|
||||
"""Init heat wrapper.
|
||||
|
||||
:param Scenario scenario: scenario instance
|
||||
:param Task task: task instance
|
||||
:param str name: stack name
|
||||
:param str template: template file path
|
||||
:param dict files: dict with file name and path
|
||||
:param dict parameters: parameters for template
|
||||
|
||||
"""
|
||||
self.scenario = scenario
|
||||
self.task = task
|
||||
self.template = open(template).read()
|
||||
self.files = {}
|
||||
self.parameters = parameters
|
||||
for name, path in files.items():
|
||||
self.files[name] = open(path).read()
|
||||
|
||||
def _wait(self, ready_statuses, failure_statuses):
|
||||
self.stack = utils.wait_for_status(
|
||||
self.stack,
|
||||
check_interval=10,
|
||||
timeout=1200,
|
||||
ready_statuses=ready_statuses,
|
||||
failure_statuses=failure_statuses,
|
||||
update_resource=utils.get_from_manager(),
|
||||
)
|
||||
|
||||
def create(self):
|
||||
with atomic.ActionTimer(self.scenario, "heat.create"):
|
||||
self.stack = self.scenario.clients("heat").stacks.create(
|
||||
stack_name=self.scenario.generate_random_name(),
|
||||
template=self.template,
|
||||
files=self.files,
|
||||
parameters=self.parameters)
|
||||
self.stack_id = self.stack["stack"]["id"]
|
||||
self.stack = self.scenario.clients(
|
||||
"heat").stacks.get(self.stack_id)
|
||||
self._wait(["CREATE_COMPLETE"], ["CREATE_FAILED"])
|
||||
|
||||
def update(self, data):
|
||||
self.parameters.update(data)
|
||||
with atomic.ActionTimer(self.scenario, "heat.update"):
|
||||
self.scenario.clients("heat").stacks.update(
|
||||
self.stack_id, template=self.template,
|
||||
files=self.files, parameters=self.parameters)
|
||||
self._wait(["UPDATE_COMPLETE"], ["UPDATE_FAILED"])
|
0
rally/plugins/workload/__init__.py
Normal file
0
rally/plugins/workload/__init__.py
Normal file
58
rally/plugins/workload/siege.py
Normal file
58
rally/plugins/workload/siege.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Run HTTP benchmark by runcommand_heat scenario."""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
SIEGE_RE = re.compile(r"^(Throughput|Transaction rate):\s+(\d+\.\d+)\s+.*")
|
||||
|
||||
|
||||
def get_instances():
|
||||
outputs = json.load(sys.stdin)
|
||||
for output in outputs:
|
||||
if output["output_key"] == "wp_nodes":
|
||||
for node in output["output_value"].values():
|
||||
yield node["wordpress-network"][0]
|
||||
|
||||
|
||||
def generate_urls_list(instances):
|
||||
urls = tempfile.NamedTemporaryFile(delete=False)
|
||||
with urls:
|
||||
for inst in instances:
|
||||
for i in range(1, 1000):
|
||||
urls.write("http://%s/wordpress/index.php/%d/\n" % (inst, i))
|
||||
return urls.name
|
||||
|
||||
|
||||
def run():
|
||||
instances = list(get_instances())
|
||||
urls = generate_urls_list(instances)
|
||||
out = subprocess.check_output("siege -q -t 60S -b -f %s" % urls,
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
for line in out.splitlines():
|
||||
m = SIEGE_RE.match(line)
|
||||
if m:
|
||||
sys.stdout.write("%s:%s\n" % m.groups())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run())
|
48
samples/tasks/scenarios/workload/wordpress.json
Normal file
48
samples/tasks/scenarios/workload/wordpress.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"VMTasks.runcommand_heat": [
|
||||
{
|
||||
"runner": {
|
||||
"type": "constant",
|
||||
"concurrency": 1,
|
||||
"timeout": 3000,
|
||||
"times": 1
|
||||
},
|
||||
"args": {
|
||||
"files": {
|
||||
"wp-instances.yaml": "rally-jobs/extra/workload/wp-instances.yaml"
|
||||
},
|
||||
"workload": {
|
||||
"username": "fedora",
|
||||
"resource": [
|
||||
"rally.plugins.workload",
|
||||
"siege.py"
|
||||
]
|
||||
},
|
||||
"template": "rally-jobs/extra/workload/wordpress_heat_template.yaml",
|
||||
"parameters": {
|
||||
"router_id": "c497caa1-9d73-402b-bcd1-cc269e9af29e",
|
||||
"instance_type": "gig",
|
||||
"wp_image": "fedora",
|
||||
"network_id": "9d477754-e9ba-4560-9b2b-9ce9d36638ce",
|
||||
"image": "fedora",
|
||||
"wp_instance_type": "gig",
|
||||
"wp_instances_count": 2
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"flavors": [
|
||||
{
|
||||
"vcpus": 1,
|
||||
"disk": 4,
|
||||
"ram": 1024,
|
||||
"name": "gig"
|
||||
}
|
||||
],
|
||||
"users": {
|
||||
"users_per_tenant": 1,
|
||||
"tenants": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
35
samples/tasks/scenarios/workload/wordpress.yaml
Normal file
35
samples/tasks/scenarios/workload/wordpress.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
|
||||
VMTasks.runcommand_heat:
|
||||
-
|
||||
args:
|
||||
workload:
|
||||
resource: ["rally.plugins.workload", "siege.py"]
|
||||
username: "fedora"
|
||||
template: rally-jobs/extra/workload/wordpress_heat_template.yaml
|
||||
files:
|
||||
wp-instances.yaml: rally-jobs/extra/workload/wp-instances.yaml
|
||||
parameters:
|
||||
wp_instances_count: 2
|
||||
wp_instance_type: gig
|
||||
instance_type: gig
|
||||
wp_image: fedora
|
||||
image: fedora
|
||||
network_id: 9d477754-e9ba-4560-9b2b-9ce9d36638ce
|
||||
router_id: c497caa1-9d73-402b-bcd1-cc269e9af29e
|
||||
|
||||
context:
|
||||
users:
|
||||
tenants: 1
|
||||
users_per_tenant: 1
|
||||
flavors:
|
||||
- name: gig
|
||||
ram: 1024
|
||||
disk: 4
|
||||
vcpus: 1
|
||||
|
||||
runner:
|
||||
concurrency: 1
|
||||
timeout: 3000
|
||||
times: 1
|
||||
type: constant
|
@ -17,6 +17,7 @@ import copy
|
||||
import mock
|
||||
import six
|
||||
|
||||
from rally import exceptions
|
||||
from rally.plugins.openstack.context.ceilometer import samples
|
||||
from rally.plugins.openstack.scenarios.ceilometer import utils as ceilo_utils
|
||||
from tests.unit import test
|
||||
@ -100,6 +101,22 @@ class CeilometerSampleGeneratorTestCase(test.TestCase):
|
||||
inst = samples.CeilometerSampleGenerator(context)
|
||||
self.assertEqual(inst.config, context["config"]["ceilometer"])
|
||||
|
||||
def test__store_batch_samples(self):
|
||||
tenants_count = 2
|
||||
users_per_tenant = 2
|
||||
resources_per_tenant = 2
|
||||
samples_per_resource = 2
|
||||
|
||||
tenants, real_context = self._gen_context(
|
||||
tenants_count, users_per_tenant,
|
||||
resources_per_tenant, samples_per_resource)
|
||||
ceilometer_ctx = samples.CeilometerSampleGenerator(real_context)
|
||||
scenario = ceilo_utils.CeilometerScenario(real_context)
|
||||
self.assertRaises(
|
||||
exceptions.ContextSetupFailure,
|
||||
ceilometer_ctx._store_batch_samples,
|
||||
scenario, ["foo", "bar"], 1)
|
||||
|
||||
def test_setup(self):
|
||||
tenants_count = 2
|
||||
users_per_tenant = 2
|
||||
|
@ -144,3 +144,33 @@ class VMTasksTestCase(test.ScenarioTestCase):
|
||||
"script_file": "foo_script",
|
||||
"interpreter": "bar_interpreter"}
|
||||
)
|
||||
|
||||
@mock.patch("rally.plugins.openstack.scenarios.vm.vmtasks.heat")
|
||||
@mock.patch("rally.plugins.openstack.scenarios.vm.vmtasks.sshutils")
|
||||
def test_runcommand_heat(self, mock_sshutils, mock_heat):
|
||||
fake_ssh = mock.Mock()
|
||||
fake_ssh.execute.return_value = [0, "key:val", ""]
|
||||
mock_sshutils.SSH.return_value = fake_ssh
|
||||
fake_stack = mock.Mock()
|
||||
fake_stack.stack.outputs = [{"output_key": "gate_node",
|
||||
"output_value": "ok"}]
|
||||
mock_heat.main.Stack.return_value = fake_stack
|
||||
context = {
|
||||
"user": {"keypair": {"name": "name", "private": "pk"},
|
||||
"credential": "ok"},
|
||||
"tenant": {"networks": [{"router_id": "1"}]}
|
||||
}
|
||||
scenario = vmtasks.VMTasks(context)
|
||||
scenario.generate_random_name = mock.Mock(return_value="name")
|
||||
scenario.add_output = mock.Mock()
|
||||
workload = {"username": "admin",
|
||||
"resource": ["foo", "bar"]}
|
||||
scenario.runcommand_heat(workload, "template",
|
||||
{"file_key": "file_value"},
|
||||
{"param_key": "param_value"})
|
||||
expected = {"chart_plugin": "Table",
|
||||
"data": {"rows": [["key", "val"]],
|
||||
"cols": ["key", "value"]},
|
||||
"description": "Data generated by workload",
|
||||
"title": "Workload summary"}
|
||||
scenario.add_output.assert_called_once_with(complete=expected)
|
||||
|
0
tests/unit/plugins/openstack/services/__init__.py
Normal file
0
tests/unit/plugins/openstack/services/__init__.py
Normal file
105
tests/unit/plugins/openstack/services/heat/test_main.py
Normal file
105
tests/unit/plugins/openstack/services/heat/test_main.py
Normal file
@ -0,0 +1,105 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from rally.plugins.openstack.services.heat import main
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class Stack(main.Stack):
|
||||
def __init__(self):
|
||||
self.scenario = mock.Mock()
|
||||
|
||||
|
||||
class StackTestCase(test.ScenarioTestCase):
|
||||
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.open",
|
||||
create=True)
|
||||
def test___init__(self, mock_open):
|
||||
reads = [mock.Mock(), mock.Mock()]
|
||||
reads[0].read.return_value = "template_contents"
|
||||
reads[1].read.return_value = "file1_contents"
|
||||
mock_open.side_effect = reads
|
||||
stack = main.Stack("scenario", "task", "template",
|
||||
parameters="parameters",
|
||||
files={"f1_name": "f1_path"})
|
||||
self.assertEqual("template_contents", stack.template)
|
||||
self.assertEqual({"f1_name": "file1_contents"}, stack.files)
|
||||
self.assertEqual([mock.call("template"), mock.call("f1_path")],
|
||||
mock_open.mock_calls)
|
||||
reads[0].read.assert_called_once_with()
|
||||
reads[1].read.assert_called_once_with()
|
||||
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.utils")
|
||||
def test__wait(self, mock_utils):
|
||||
fake_stack = mock.Mock()
|
||||
stack = Stack()
|
||||
stack.stack = fake_stack = mock.Mock()
|
||||
stack._wait(["ready_statuses"], ["failure_statuses"])
|
||||
mock_utils.wait_for_status.assert_called_once_with(
|
||||
fake_stack, check_interval=10,
|
||||
ready_statuses=["ready_statuses"],
|
||||
failure_statuses=["failure_statuses"],
|
||||
timeout=1200,
|
||||
update_resource=mock_utils.get_from_manager())
|
||||
|
||||
@mock.patch("rally.task.atomic")
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.open")
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
|
||||
def test_create(self, mock_stack__wait, mock_open, mock_task_atomic):
|
||||
mock_scenario = mock.MagicMock()
|
||||
mock_scenario.generate_random_name.return_value = "fake_name"
|
||||
mock_open().read.return_value = "fake_content"
|
||||
mock_new_stack = {
|
||||
"stack": {
|
||||
"id": "fake_id"
|
||||
}
|
||||
}
|
||||
mock_scenario.clients("heat").stacks.create.return_value = (
|
||||
mock_new_stack)
|
||||
|
||||
stack = main.Stack(
|
||||
scenario=mock_scenario, task=mock.Mock(),
|
||||
template=mock.Mock(), files={}
|
||||
)
|
||||
stack.create()
|
||||
mock_scenario.clients("heat").stacks.create.assert_called_once_with(
|
||||
files={}, parameters=None, stack_name="fake_name",
|
||||
template="fake_content"
|
||||
)
|
||||
mock_scenario.clients("heat").stacks.get.assert_called_once_with(
|
||||
"fake_id")
|
||||
mock_stack__wait.assert_called_once_with(["CREATE_COMPLETE"],
|
||||
["CREATE_FAILED"])
|
||||
|
||||
@mock.patch("rally.task.atomic")
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.open")
|
||||
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
|
||||
def test_update(self, mock_stack__wait, mock_open, mock_task_atomic):
|
||||
mock_scenario = mock.MagicMock(stack_id="fake_id")
|
||||
mock_parameters = mock.Mock()
|
||||
mock_open().read.return_value = "fake_content"
|
||||
stack = main.Stack(
|
||||
scenario=mock_scenario, task=mock.Mock(),
|
||||
template=None, files={}, parameters=mock_parameters
|
||||
)
|
||||
stack.stack_id = "fake_id"
|
||||
stack.parameters = mock_parameters
|
||||
stack.update({"foo": "bar"})
|
||||
mock_scenario.clients("heat").stacks.update.assert_called_once_with(
|
||||
"fake_id", files={}, template="fake_content",
|
||||
parameters=mock_parameters
|
||||
)
|
||||
mock_stack__wait.assert_called_once_with(["UPDATE_COMPLETE"],
|
||||
["UPDATE_FAILED"])
|
0
tests/unit/plugins/workload/__init__.py
Normal file
0
tests/unit/plugins/workload/__init__.py
Normal file
82
tests/unit/plugins/workload/test_siege.py
Normal file
82
tests/unit/plugins/workload/test_siege.py
Normal file
@ -0,0 +1,82 @@
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from rally.plugins.workload import siege
|
||||
from tests.unit import test
|
||||
|
||||
import mock
|
||||
|
||||
SIEGE_OUTPUT = """
|
||||
Transactions: 522 hits
|
||||
Availability: 100.00 %
|
||||
Elapsed time: 3.69 secs
|
||||
Data transferred: 1.06 MB
|
||||
Response time: 0.10 secs
|
||||
Transaction rate: 141.46 trans/sec
|
||||
Throughput: 0.29 MB/sec
|
||||
Concurrency: 14.71
|
||||
Successful transactions: 522
|
||||
Failed transactions: 0
|
||||
Longest transaction: 0.26
|
||||
Shortest transaction: 0.08
|
||||
"""
|
||||
|
||||
OUTPUT = [
|
||||
{"output_value": "curl", "descr": "", "output_key": "curl_cli"},
|
||||
{"output_value": "wp-net", "descr": "", "output_key": "net_name"},
|
||||
{"output_value": ["10.0.0.3", "172.16.0.159"],
|
||||
"description": "",
|
||||
"output_key": "gate_node"},
|
||||
{"output_value": {
|
||||
"1": {"wordpress-network": ["10.0.0.4"]},
|
||||
"0": {"wordpress-network": ["10.0.0.5"]}},
|
||||
"description": "No description given", "output_key": "wp_nodes"}]
|
||||
|
||||
|
||||
class SiegeTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("rally.plugins.workload.siege.json.load")
|
||||
def test_get_instances(self, mock_load):
|
||||
mock_load.return_value = OUTPUT
|
||||
instances = list(siege.get_instances())
|
||||
self.assertEqual(["10.0.0.4", "10.0.0.5"], instances)
|
||||
|
||||
@mock.patch("rally.plugins.workload.siege.get_instances")
|
||||
@mock.patch("rally.plugins.workload.siege.generate_urls_list")
|
||||
@mock.patch("rally.plugins.workload.siege.subprocess.check_output")
|
||||
def test_run(self, mock_check_output, mock_generate_urls_list,
|
||||
mock_get_instances):
|
||||
mock_get_instances.return_value = [1, 2]
|
||||
mock_generate_urls_list.return_value = "urls"
|
||||
mock_check_output.return_value = SIEGE_OUTPUT
|
||||
mock_write = mock.MagicMock()
|
||||
mock_stdout = mock.MagicMock(write=mock_write)
|
||||
real_stdout = sys.stdout
|
||||
sys.stdout = mock_stdout
|
||||
siege.run()
|
||||
expected = [mock.call("Transaction rate:141.46\n"),
|
||||
mock.call("Throughput:0.29\n")]
|
||||
sys.stdout = real_stdout
|
||||
self.assertEqual(expected, mock_write.mock_calls)
|
||||
|
||||
@mock.patch("rally.plugins.workload.siege.tempfile.NamedTemporaryFile")
|
||||
def test_generate_urls_list(self, mock_named_temporary_file):
|
||||
mock_urls = mock.MagicMock()
|
||||
mock_named_temporary_file.return_value = mock_urls
|
||||
name = siege.generate_urls_list(["foo", "bar"])
|
||||
self.assertEqual(mock_urls.name, name)
|
Loading…
Reference in New Issue
Block a user