Add heat siege workload scenario

Change-Id: I2621f4f75eac394951081270338bd63dc43b599e
This commit is contained in:
Sergey Skripnick 2016-01-26 13:54:50 +01:00 committed by Boris Pavlovic
parent 4f7b42de67
commit 9de7fdce77
18 changed files with 854 additions and 0 deletions

View 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'] }

View 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'] }

View File

@ -826,3 +826,38 @@
sla: sla:
failure_rate: failure_rate:
max: 0 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

View File

@ -14,12 +14,16 @@
# under the License. # under the License.
import json import json
import pkgutil
from rally.common import logging from rally.common import logging
from rally.common import sshutils
from rally import consts from rally import consts
from rally import exceptions from rally import exceptions
from rally.plugins.openstack import scenario from rally.plugins.openstack import scenario
from rally.plugins.openstack.scenarios.vm import utils as vm_utils 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 types
from rally.task import validation from rally.task import validation
@ -204,3 +208,66 @@ class VMTasks(vm_utils.VMScenario):
return self.boot_runcommand_delete( return self.boot_runcommand_delete(
image=self.context["tenant"]["custom_image"]["id"], **kwargs) 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}}
)

View 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"])

View File

View 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())

View 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
}
}
}
]
}

View 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

View File

@ -17,6 +17,7 @@ import copy
import mock import mock
import six import six
from rally import exceptions
from rally.plugins.openstack.context.ceilometer import samples from rally.plugins.openstack.context.ceilometer import samples
from rally.plugins.openstack.scenarios.ceilometer import utils as ceilo_utils from rally.plugins.openstack.scenarios.ceilometer import utils as ceilo_utils
from tests.unit import test from tests.unit import test
@ -100,6 +101,22 @@ class CeilometerSampleGeneratorTestCase(test.TestCase):
inst = samples.CeilometerSampleGenerator(context) inst = samples.CeilometerSampleGenerator(context)
self.assertEqual(inst.config, context["config"]["ceilometer"]) 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): def test_setup(self):
tenants_count = 2 tenants_count = 2
users_per_tenant = 2 users_per_tenant = 2

View File

@ -144,3 +144,33 @@ class VMTasksTestCase(test.ScenarioTestCase):
"script_file": "foo_script", "script_file": "foo_script",
"interpreter": "bar_interpreter"} "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)

View 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"])

View File

View 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)