[Plugins] Improve scenario output from boot_runcommand_delete

Scenario VMTasks.boot_runcommand_delete now can populate
report tab "Scenario Data" with any allowed charts.
SSH command return value structure new reflects
Scenario.add_output() arguments.

Also, there is new shell script
samples/tasks/support/instance_test.sh which generates
different load on server and creates proper JSON output.

Old format of command output is also supported but marked
as deprecated.

Change-Id: I6a34b634ea7d9080f9ca08bf0f82d04fa69406d9
This commit is contained in:
Alexander Maretskiy 2016-06-10 19:06:49 +03:00
parent 057fb25bba
commit ca4f489b9e
7 changed files with 304 additions and 49 deletions

View File

@ -0,0 +1,98 @@
#!/bin/sh
# Load server and output JSON results ready to be processed
# by Rally scenario
get_used_cpu_percent() {
echo 100 $(top -b -n 1 | grep -i CPU | head -n 1 | awk '{print $8}' | tr -d %) - p | dc
}
get_used_ram_percent() {
local total=$(free | grep Mem: | awk '{print $2}')
local used=$(free | grep -- -/+\ buffers | awk '{print $3}')
echo ${used} 100 \* ${total} / p | dc
}
get_used_disk_percent() {
df -P / | grep -v Filesystem | awk '{print $5}' | tr -d %
}
get_seconds() {
(time -p ${1}) 2>&1 | awk '/real/{print $2}'
}
complete_load() {
local script_file=${LOAD_SCRIPT_FILE:-/tmp/load.sh}
local stop_file=${LOAD_STOP_FILE:-/tmp/load.stop}
local processes_num=${LOAD_PROCESSES_COUNT:-20}
local size=${LOAD_SIZE_MB:-5}
cat << EOF > ${script_file}
until test -e ${stop_file}
do dd if=/dev/urandom bs=1M count=${size} 2>/dev/null | gzip >/dev/null ; done
EOF
local sep
local cpu
local ram
local dis
rm -f ${stop_file}
for i in $(seq ${processes_num})
do
i=$((i-1))
sh ${script_file} &
cpu="${cpu}${sep}[${i}, $(get_used_cpu_percent)]"
ram="${ram}${sep}[${i}, $(get_used_ram_percent)]"
dis="${dis}${sep}[${i}, $(get_used_disk_percent)]"
sep=", "
done
> ${stop_file}
cat << EOF
{
"title": "Generate load by spawning processes",
"description": "Each process runs gzip for ${size}M urandom data in a loop",
"chart_plugin": "Lines",
"axis_label": "Number of processes",
"label": "Usage, %",
"data": [
["CPU", [${cpu}]],
["Memory", [${ram}]],
["Disk", [${dis}]]]
}
EOF
}
additive_dd() {
local c=${1:-50} # Megabytes
local file=/tmp/dd_test.img
local write=$(get_seconds "dd if=/dev/urandom of=${file} bs=1M count=${c}")
local read=$(get_seconds "dd if=${file} of=/dev/null bs=1M count=${c}")
local gzip=$(get_seconds "gzip ${file}")
rm ${file}.gz
cat << EOF
{
"title": "Write, read and gzip file",
"description": "Using file '${file}', size ${c}Mb.",
"chart_plugin": "StackedArea",
"data": [
["write_${c}M", ${write}],
["read_${c}M", ${read}],
["gzip_${c}M", ${gzip}]]
},
{
"title": "Statistics for write/read/gzip",
"chart_plugin": "StatsTable",
"data": [
["write_${c}M", ${write}],
["read_${c}M", ${read}],
["gzip_${c}M", ${gzip}]]
}
EOF
}
cat << EOF
{
"additive": [$(additive_dd)],
"complete": [$(complete_load)]
}
EOF

View File

@ -670,6 +670,28 @@
max: 0
VMTasks.boot_runcommand_delete:
-
args:
flavor:
name: "m1.tiny"
image:
name: {{image_name}}
command:
script_file: "~/.rally/extra/instance_test.sh"
interpreter: "/bin/sh"
username: "cirros"
runner:
type: "constant"
times: {{smoke or 4}}
concurrency: {{smoke or 2}}
context:
users:
tenants: {{smoke or 2}}
users_per_tenant: {{smoke or 2}}
network: {}
sla:
failure_rate:
max: 0
-
args:
flavor:
@ -689,6 +711,9 @@
tenants: {{smoke or 2}}
users_per_tenant: {{smoke or 2}}
network: {}
sla:
failure_rate:
max: 0
VMTasks.boot_runcommand_delete_custom_image:
-

View File

@ -190,21 +190,38 @@ class VMTasks(vm_utils.VMScenario):
self._delete_server_with_fip(server, fip,
force_delete=force_delete)
# NOTE(amaretskiy): command output should be in format:
# {"key1": numeric_value, "key2": numeric_value, ...}
output = None
if type(data) == dict:
if type(data) != dict:
raise exceptions.ScriptError(
"Command has returned data in unexpected format.\n"
"Expected format: {"
"\"additive\": [{chart data}, {chart data}, ...], "
"\"complete\": [{chart data}, {chart data}, ...]}\n"
"Actual data: %s" % data)
if set(data) - {"additive", "complete"}:
LOG.warning(
"Deprecated since Rally release 0.4.1: command has "
"returned data in format {\"key\": <value>, ...}\n"
"Expected format: {"
"\"additive\": [{chart data}, {chart data}, ...], "
"\"complete\": [{chart data}, {chart data}, ...]}")
output = None
try:
output = [[str(k), float(v)] for k, v in data.items()]
except (TypeError, ValueError):
LOG.error(("Command has returned data in unexpected format.\n"
"Expected format: {key1: numeric_value, "
"key2: numeric_value, ...}.\n"
"Actual data: %s" % data))
if output:
self.add_output(additive={"title": "Command output",
"chart_plugin": "Lines",
"data": output})
raise exceptions.ScriptError(
"Command has returned data in unexpected format.\n"
"Expected format: {key1: <number>, "
"key2: <number>, ...}.\n"
"Actual data: %s" % data)
if output:
self.add_output(additive={"title": "Command output",
"chart_plugin": "Lines",
"data": output})
else:
for chart_type, charts in data.items():
for chart in charts:
self.add_output(**{chart_type: chart})
@types.convert(image={"type": "glance_image"},
flavor={"type": "nova_flavor"})

View File

@ -13,7 +13,7 @@
"force_delete": false,
"command": {
"interpreter": "/bin/sh",
"script_file": "samples/tasks/support/instance_dd_test.sh"
"script_file": "samples/tasks/support/instance_test.sh"
},
"username": "cirros"
},

View File

@ -11,7 +11,7 @@
force_delete: false
command:
interpreter: "/bin/sh"
script_file: "samples/tasks/support/instance_dd_test.sh"
script_file: "samples/tasks/support/instance_test.sh"
username: "cirros"
runner:
type: "constant"

View File

@ -0,0 +1,98 @@
#!/bin/sh
# Load server and output JSON results ready to be processed
# by Rally scenario
get_used_cpu_percent() {
echo 100 $(top -b -n 1 | grep -i CPU | head -n 1 | awk '{print $8}' | tr -d %) - p | dc
}
get_used_ram_percent() {
local total=$(free | grep Mem: | awk '{print $2}')
local used=$(free | grep -- -/+\ buffers | awk '{print $3}')
echo ${used} 100 \* ${total} / p | dc
}
get_used_disk_percent() {
df -P / | grep -v Filesystem | awk '{print $5}' | tr -d %
}
get_seconds() {
(time -p ${1}) 2>&1 | awk '/real/{print $2}'
}
complete_load() {
local script_file=${LOAD_SCRIPT_FILE:-/tmp/load.sh}
local stop_file=${LOAD_STOP_FILE:-/tmp/load.stop}
local processes_num=${LOAD_PROCESSES_COUNT:-20}
local size=${LOAD_SIZE_MB:-5}
cat << EOF > ${script_file}
until test -e ${stop_file}
do dd if=/dev/urandom bs=1M count=${size} 2>/dev/null | gzip >/dev/null ; done
EOF
local sep
local cpu
local ram
local dis
rm -f ${stop_file}
for i in $(seq ${processes_num})
do
i=$((i-1))
sh ${script_file} &
cpu="${cpu}${sep}[${i}, $(get_used_cpu_percent)]"
ram="${ram}${sep}[${i}, $(get_used_ram_percent)]"
dis="${dis}${sep}[${i}, $(get_used_disk_percent)]"
sep=", "
done
> ${stop_file}
cat << EOF
{
"title": "Generate load by spawning processes",
"description": "Each process runs gzip for ${size}M urandom data in a loop",
"chart_plugin": "Lines",
"axis_label": "Number of processes",
"label": "Usage, %",
"data": [
["CPU", [${cpu}]],
["Memory", [${ram}]],
["Disk", [${dis}]]]
}
EOF
}
additive_dd() {
local c=${1:-50} # Megabytes
local file=/tmp/dd_test.img
local write=$(get_seconds "dd if=/dev/urandom of=${file} bs=1M count=${c}")
local read=$(get_seconds "dd if=${file} of=/dev/null bs=1M count=${c}")
local gzip=$(get_seconds "gzip ${file}")
rm ${file}.gz
cat << EOF
{
"title": "Write, read and gzip file",
"description": "Using file '${file}', size ${c}Mb.",
"chart_plugin": "StackedArea",
"data": [
["write_${c}M", ${write}],
["read_${c}M", ${read}],
["gzip_${c}M", ${gzip}]]
},
{
"title": "Statistics for write/read/gzip",
"chart_plugin": "StatsTable",
"data": [
["write_${c}M", ${write}],
["read_${c}M", ${read}],
["gzip_${c}M", ${gzip}]]
}
EOF
}
cat << EOF
{
"additive": [$(additive_dd)],
"complete": [$(complete_load)]
}
EOF

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from rally import exceptions
@ -20,6 +21,7 @@ from rally.plugins.openstack.scenarios.vm import vmtasks
from tests.unit import test
@ddt.ddt
class VMTasksTestCase(test.ScenarioTestCase):
def setUp(self):
@ -35,7 +37,7 @@ class VMTasksTestCase(test.ScenarioTestCase):
self.scenario._create_volume = mock.Mock(
return_value=mock.Mock(id="foo_volume"))
self.scenario._run_command = mock.MagicMock(
return_value=(0, "\"foo_out\"", "foo_err"))
return_value=(0, "{\"foo\": 42}", "foo_err"))
self.scenario.add_output = mock.Mock()
def test_boot_runcommand_delete(self):
@ -72,42 +74,57 @@ class VMTasksTestCase(test.ScenarioTestCase):
additive={"title": "Command output", "chart_plugin": "Lines",
"data": [["foo", 42.0]]})
def test_boot_runcommand_delete_command(self):
self.scenario.boot_runcommand_delete(
"foo_image", "foo_flavor",
command={"remote_path": "foo"},
username="foo_username",
password="foo_password",
use_floating_ip="use_fip",
floating_network="ext_network",
force_delete="foo_force",
volume_args={"size": 16},
foo_arg="foo_value")
@ddt.data(
{"output": (0, "", ""), "raises": exceptions.ScriptError},
{"output": (0, "{\"foo\": 42}", ""),
"expected": [{"additive": {"chart_plugin": "Lines",
"data": [["foo", 42.0]],
"title": "Command output"}}]},
{"output": (1, "{\"foo\": 42}", ""), "raises": exceptions.ScriptError},
{"output": ("", 1, ""), "raises": TypeError},
{"output": (0, "{\"additive\": [1, 2]}", ""),
"expected": [{"additive": 1}, {"additive": 2}]},
{"output": (0, "{\"complete\": [3, 4]}", ""),
"expected": [{"complete": 3}, {"complete": 4}]},
{"output": (0, "{\"additive\": [1, 2], \"complete\": [3, 4]}", ""),
"expected": [{"additive": 1}, {"additive": 2},
{"complete": 3}, {"complete": 4}]}
)
@ddt.unpack
def test_boot_runcommand_delete_add_output(self, output,
expected=None, raises=None):
self.scenario._run_command.return_value = output
kwargs = {"command": {"remote_path": "foo"},
"username": "foo_username",
"password": "foo_password",
"use_floating_ip": "use_fip",
"floating_network": "ext_network",
"force_delete": "foo_force",
"volume_args": {"size": 16},
"foo_arg": "foo_value"}
if raises:
self.assertRaises(raises, self.scenario.boot_runcommand_delete,
"foo_image", "foo_flavor", **kwargs)
self.assertFalse(self.scenario.add_output.called)
else:
self.scenario.boot_runcommand_delete("foo_image", "foo_flavor",
**kwargs)
calls = [mock.call(**kw) for kw in expected]
self.scenario.add_output.assert_has_calls(calls, any_order=True)
self.scenario._create_volume.assert_called_once_with(
16, imageRef=None)
self.scenario._boot_server_with_fip.assert_called_once_with(
"foo_image", "foo_flavor", key_name="keypair_name",
use_floating_ip="use_fip", floating_network="ext_network",
block_device_mapping={"vdrally": "foo_volume:::1"},
foo_arg="foo_value")
self.scenario._create_volume.assert_called_once_with(
16, imageRef=None)
self.scenario._boot_server_with_fip.assert_called_once_with(
"foo_image", "foo_flavor", key_name="keypair_name",
use_floating_ip="use_fip", floating_network="ext_network",
block_device_mapping={"vdrally": "foo_volume:::1"},
foo_arg="foo_value")
self.scenario._run_command.assert_called_once_with(
"foo_ip", 22, "foo_username", "foo_password",
command={"remote_path": "foo"})
self.scenario._delete_server_with_fip.assert_called_once_with(
"foo_server", self.ip, force_delete="foo_force")
def test_boot_runcommand_delete_script_fails(self):
self.scenario._run_command = mock.MagicMock(
return_value=(1, "\"foo_out\"", "foo_err"))
self.assertRaises(exceptions.ScriptError,
self.scenario.boot_runcommand_delete,
"foo_image", "foo_flavor", "foo_interpreter",
"foo_script", "foo_username")
self.scenario._delete_server_with_fip.assert_called_once_with(
"foo_server", self.ip, force_delete=False)
self.assertFalse(self.scenario.add_output.called)
self.scenario._run_command.assert_called_once_with(
"foo_ip", 22, "foo_username", "foo_password",
command={"remote_path": "foo"})
self.scenario._delete_server_with_fip.assert_called_once_with(
"foo_server", self.ip, force_delete="foo_force")
def test_boot_runcommand_delete_command_timeouts(self):
self.scenario._run_command.side_effect = exceptions.SSHTimeout()