Add new Murano scenarios

- added four new Murano scenarios which check performance of
commands which work with packages, like 'import-package', 'list-packages',
'delete-package'.
- added appropriate methods into the murano/utils.py
- added unit tests

Change-Id: I655bb0c8a0980c59cd4ab576f97ee94c2c3cd0f7
This commit is contained in:
Anastasia Kuznetsova 2015-07-09 20:19:23 +03:00
parent 79f8809245
commit a04841e71f
6 changed files with 580 additions and 2 deletions

View File

@ -5,6 +5,6 @@ Name: HelloReporter
Description: |
HelloReporter test app.
Author: 'Mirantis, Inc'
Tags: [App, Test, HelloWorld]
Tags: []
Classes:
io.murano.apps.HelloReporter: HelloReporter.yaml

View File

@ -61,6 +61,84 @@
app_package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter/"
roles:
- "admin"
MuranoPackages.import_and_list_packages:
-
args:
package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter/"
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
sla:
failure_rate:
max: 0
-
args:
package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter.zip"
runner:
type: "constant"
times: 1
concurrency: 1
context:
users:
tenants: 1
users_per_tenant: 1
sla:
failure_rate:
max: 0
MuranoPackages.import_and_delete_package:
-
args:
package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter/"
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
sla:
failure_rate:
max: 0
MuranoPackages.import_and_filter_applications:
-
args:
package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter/"
filter_query: {"category" : "Web"}
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
sla:
failure_rate:
max: 0
MuranoPackages.package_lifecycle:
-
args:
package: "/home/jenkins/.rally/extra/murano/applications/HelloReporter/io.murano.apps.HelloReporter/"
body: {"categories": ["Web"]}
operation: "add"
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
sla:
failure_rate:
max: 0

View File

@ -0,0 +1,142 @@
# Copyright 2015: Mirantis Inc.
# 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 os
from rally import consts
from rally.plugins.openstack.scenarios.murano import utils
from rally.task import scenario
from rally.task import validation
class MuranoPackages(utils.MuranoScenario):
"""Benchmark scenarios for Murano packages."""
@validation.required_parameters("package")
@validation.file_exists(param_name="package", mode=os.F_OK)
@validation.required_clients("murano")
@validation.required_services(consts.Service.MURANO)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["murano.packages"]})
def import_and_list_packages(self, package, include_disabled=False):
"""Import Murano package and get list of packages.
Measure the "murano import-package" and "murano package-list" commands
performance.
It imports Murano package from "package" (if it is not a zip archive
then zip archive will be prepared) and gets list of imported packages.
:param package: path to zip archive that represents Murano
application package or absolute path to folder with
package components
:param include_disabled: specifies whether the disabled packages will
be included in a the result or not.
Default value is False.
"""
package_path = self._zip_package(package)
try:
self._import_package(package_path)
self._list_packages(include_disabled=include_disabled)
finally:
os.remove(package_path)
@validation.required_parameters("package")
@validation.file_exists(param_name="package", mode=os.F_OK)
@validation.required_clients("murano")
@validation.required_services(consts.Service.MURANO)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["murano.packages"]})
def import_and_delete_package(self, package):
"""Import Murano package and then delete it.
Measure the "murano import-package" and "murano package-delete"
commands performance.
It imports Murano package from "package" (if it is not a zip archive
then zip archive will be prepared) and deletes it.
:param package: path to zip archive that represents Murano
application package or absolute path to folder with
package components
"""
package_path = self._zip_package(package)
try:
package = self._import_package(package_path)
self._delete_package(package)
finally:
os.remove(package_path)
@validation.required_parameters("package", "body")
@validation.file_exists(param_name="package", mode=os.F_OK)
@validation.required_clients("murano")
@validation.required_services(consts.Service.MURANO)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["murano.packages"]})
def package_lifecycle(self, package, body, operation="replace"):
"""Import Murano package, modify it and then delete it.
Measure the Murano import, update and delete package
commands performance.
It imports Murano package from "package" (if it is not a zip archive
then zip archive will be prepared), modifies it (using data from
"body") and deletes.
:param package: path to zip archive that represents Murano
application package or absolute path to folder with
package components
:param body: dict object that defines what package property will be
updated, e.g {"tags": ["tag"]} or {"enabled": "true"}
:param operation: string object that defines the way of how package
property will be updated, allowed operations are
"add", "replace" or "delete".
Default value is "replace".
"""
package_path = self._zip_package(package)
try:
package = self._import_package(package_path)
self._update_package(package, body, operation)
self._delete_package(package)
finally:
os.remove(package_path)
@validation.required_parameters("package", "filter_query")
@validation.file_exists(param_name="package", mode=os.F_OK)
@validation.required_clients("murano")
@validation.required_services(consts.Service.MURANO)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["murano.packages"]})
def import_and_filter_applications(self, package, filter_query):
"""Import Murano package and then filter packages by some criteria.
Measure the performance of package import and package
filtering commands.
It imports Murano package from "package" (if it is not a zip archive
then zip archive will be prepared) and filters packages by some
criteria.
:param package: path to zip archive that represents Murano
application package or absolute path to folder with
package components
:param filter_query: dict that contains filter criteria, lately it
will be passed as **kwargs to filter method
e.g. {"category": "Web"}
"""
package_path = self._zip_package(package)
try:
self._import_package(package_path)
self._filter_applications(filter_query)
finally:
os.remove(package_path)

View File

@ -13,10 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import shutil
import tempfile
import uuid
import zipfile
from oslo_config import cfg
import yaml
from rally.common import fileutils
from rally.common import utils as common_utils
from rally.plugins.openstack import scenario
from rally.task import atomic
from rally.task import utils
@ -31,7 +38,7 @@ MURANO_TIMEOUT_OPTS = [
cfg.IntOpt("delete_environment_check_interval", default=2,
help="Delete environment check interval in seconds"),
cfg.IntOpt("deploy_environment_check_interval", default=5,
help="Deploy environment check interval in seconds")
help="Deploy environment check interval in seconds"),
]
benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options")
@ -125,3 +132,149 @@ class MuranoScenario(scenario.OpenStackScenario):
timeout=CONF.benchmark.deploy_environment_timeout,
check_interval=CONF.benchmark.deploy_environment_check_interval
)
@atomic.action_timer("murano.list_packages")
def _list_packages(self, include_disabled=False):
"""Returns packages list.
:param include_disabled: if "True" then disabled packages will be
included in a the result.
Default value is False.
:returns: list of imported packages
"""
return self.clients("murano").packages.list(
include_disabled=include_disabled)
@atomic.action_timer("murano.import_package")
def _import_package(self, package):
"""Import package to the Murano.
:param package: path to zip archive with Murano application
:returns: imported package
"""
package = self.clients("murano").packages.create(
{}, {"file": open(package)}
)
return package
@atomic.action_timer("murano.delete_package")
def _delete_package(self, package):
"""Delete specified package.
:param package: package that will be deleted
"""
self.clients("murano").packages.delete(package.id)
@atomic.action_timer("murano.update_package")
def _update_package(self, package, body, operation="replace"):
"""Update specified package.
:param package: package that will be updated
:param body: dict object that defines what package property will be
updated, e.g {"tags": ["tag"]} or {"enabled": "true"}
:param operation: string object that defines the way of how package
property will be updated, allowed operations are
"add", "replace" or "delete".
Default value is "replace".
:returns: updated package
"""
return self.clients("murano").packages.update(
package.id, body, operation)
@atomic.action_timer("murano.filter_applications")
def _filter_applications(self, filter_query):
"""Filter list of uploaded application by specified criteria.
:param filter_query: dict that contains filter criteria, it
will be passed as **kwargs to filter method
e.g. {"category": "Web"}
:returns: filtered list of packages
"""
return self.clients("murano").packages.filter(**filter_query)
def _zip_package(self, package_path):
"""Call _prepare_package method that returns path to zip archive."""
return MuranoPackageManager()._prepare_package(package_path)
class MuranoPackageManager(object):
@staticmethod
def _read_from_file(filename):
with open(filename, "r") as f:
read_data = f.read()
return yaml.safe_load(read_data)
@staticmethod
def _write_to_file(data, filename):
with open(filename, "w") as f:
yaml.safe_dump(data, f)
def _change_app_fullname(self, app_dir):
"""Change application full name.
To avoid name conflict error during package import (when user
tries to import a few packages into the same tenant) need to change the
application name. For doing this need to replace following parts
in manifest.yaml
from
...
FullName: app.name
...
Classes:
app.name: app_class.yaml
to:
...
FullName: <new_name>
...
Classes:
<new_name>: app_class.yaml
:param app_dir: path to directory with Murano application context
"""
new_fullname = common_utils.generate_random_name("app.")
manifest_file = os.path.join(app_dir, "manifest.yaml")
manifest = self._read_from_file(manifest_file)
class_file_name = manifest["Classes"][manifest["FullName"]]
# update manifest.yaml file
del manifest["Classes"][manifest["FullName"]]
manifest["FullName"] = new_fullname
manifest["Classes"][new_fullname] = class_file_name
self._write_to_file(manifest, manifest_file)
def _prepare_package(self, package_path):
"""Check whether the package path is path to zip archive or not.
If package_path is not a path to zip archive but path to Murano
application folder, than method prepares zip archive with Murano
application. It copies directory with Murano app files to temporary
folder, changes manifest.yaml and class file (to avoid '409 Conflict'
errors in Murano) and prepares zip package.
:param package_path: path to zip archive or directory with package
components
:returns: path to zip archive with Murano application
"""
if not zipfile.is_zipfile(package_path):
tmp_dir = tempfile.mkdtemp()
pkg_dir = os.path.join(tmp_dir, "package/")
try:
shutil.copytree(package_path, pkg_dir)
self._change_app_fullname(pkg_dir)
package_path = fileutils.pack_dir(pkg_dir)
finally:
shutil.rmtree(tmp_dir)
return package_path

View File

@ -0,0 +1,79 @@
# Copyright 2015: Mirantis Inc.
# 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 mock
from rally.plugins.openstack.scenarios.murano import packages
from tests.unit import test
MURANO_SCENARIO = ("rally.plugins.openstack.scenarios.murano."
"packages.MuranoPackages")
class MuranoPackagesTestCase(test.TestCase):
def setUp(self):
super(MuranoPackagesTestCase, self).setUp()
self.scenario = packages.MuranoPackages()
self.scenario._import_package = mock.Mock()
self.scenario._zip_package = mock.Mock()
self.scenario._list_packages = mock.Mock()
self.scenario._delete_package = mock.Mock()
self.scenario._update_package = mock.Mock()
self.scenario._filter_applications = mock.Mock()
self.mock_remove = mock.patch("os.remove")
self.mock_remove.start()
def tearDown(self):
super(MuranoPackagesTestCase, self).tearDown()
self.mock_remove.stop()
def test_make_zip_import_and_list_packages(self):
self.scenario.import_and_list_packages("foo_package.zip")
self.scenario._import_package.assert_called_once_with(
self.scenario._zip_package.return_value)
self.scenario._zip_package.assert_called_once_with("foo_package.zip")
self.scenario._list_packages.assert_called_once_with(
include_disabled=False)
def test_import_and_delete_package(self):
fake_package = mock.Mock()
self.scenario._import_package.return_value = fake_package
self.scenario.import_and_delete_package("foo_package.zip")
self.scenario._import_package.assert_called_once_with(
self.scenario._zip_package.return_value)
self.scenario._delete_package.assert_called_once_with(fake_package)
def test_package_lifecycle(self):
fake_package = mock.Mock()
self.scenario._import_package.return_value = fake_package
self.scenario.package_lifecycle(
"foo_package.zip", {"category": "Web"}, "add")
self.scenario._import_package.assert_called_once_with(
self.scenario._zip_package.return_value)
self.scenario._update_package.assert_called_once_with(
fake_package, {"category": "Web"}, "add")
self.scenario._delete_package.assert_called_once_with(fake_package)
def test_import_and_filter_applications(self):
fake_package = mock.Mock()
self.scenario._import_package.return_value = fake_package
self.scenario.import_and_filter_applications(
"foo_package.zip", {"category": "Web"})
self.scenario._import_package.assert_called_once_with(
self.scenario._zip_package.return_value)
self.scenario._filter_applications.assert_called_once_with(
{"category": "Web"}
)

View File

@ -105,3 +105,129 @@ class MuranoScenarioTestCase(test.ScenarioTestCase):
self.mock_resource_is.mock.assert_called_once_with("READY")
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.deploy_environment")
@mock.patch(MRN_UTILS + ".open",
side_effect=mock.mock_open(read_data="Key: value"),
create=True)
def test_read_from_file(self, mock_open):
utility = utils.MuranoPackageManager()
data = utility._read_from_file("filename")
expected_data = {"Key": "value"}
self.assertEqual(expected_data, data)
@mock.patch(MRN_UTILS + ".MuranoPackageManager._read_from_file")
@mock.patch(MRN_UTILS + ".MuranoPackageManager._write_to_file")
def test_change_app_fullname(
self, mock_murano_package_manager__write_to_file,
mock_murano_package_manager__read_from_file):
manifest = {"FullName": "app.name_abc",
"Classes": {"app.name_abc": "app_class.yaml"}}
mock_murano_package_manager__read_from_file.side_effect = (
[manifest])
utility = utils.MuranoPackageManager()
utility._change_app_fullname("tmp/tmpfile/")
mock_murano_package_manager__read_from_file.assert_has_calls(
[mock.call("tmp/tmpfile/manifest.yaml")]
)
mock_murano_package_manager__write_to_file.assert_has_calls(
[mock.call(manifest, "tmp/tmpfile/manifest.yaml")]
)
@mock.patch("zipfile.is_zipfile")
@mock.patch("tempfile.mkdtemp")
@mock.patch("shutil.copytree")
@mock.patch(MRN_UTILS + ".MuranoPackageManager._change_app_fullname")
@mock.patch("rally.common.fileutils.pack_dir")
@mock.patch("shutil.rmtree")
def test_prepare_zip_if_not_zip(
self, mock_shutil_rmtree, mock_pack_dir,
mock_murano_package_manager__change_app_fullname,
mock_shutil_copytree, mock_tempfile_mkdtemp,
mock_zipfile_is_zipfile):
utility = utils.MuranoPackageManager()
package_path = "tmp/tmpfile"
mock_zipfile_is_zipfile.return_value = False
mock_tempfile_mkdtemp.return_value = "tmp/tmpfile"
mock_pack_dir.return_value = "tmp/tmpzipfile"
zip_file = utility._prepare_package(package_path)
self.assertEqual("tmp/tmpzipfile", zip_file)
mock_tempfile_mkdtemp.assert_called_once_with()
mock_shutil_copytree.assert_called_once_with(
"tmp/tmpfile",
"tmp/tmpfile/package/"
)
(mock_murano_package_manager__change_app_fullname.
assert_called_once_with("tmp/tmpfile/package/"))
mock_shutil_rmtree.assert_called_once_with("tmp/tmpfile")
@mock.patch("zipfile.is_zipfile")
def test_prepare_zip_if_zip(self, mock_zipfile_is_zipfile):
utility = utils.MuranoPackageManager()
package_path = "tmp/tmpfile.zip"
mock_zipfile_is_zipfile.return_value = True
zip_file = utility._prepare_package(package_path)
self.assertEqual("tmp/tmpfile.zip", zip_file)
def test_list_packages(self):
scenario = utils.MuranoScenario()
self.assertEqual(self.clients("murano").packages.list.return_value,
scenario._list_packages())
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.list_packages")
@mock.patch(MRN_UTILS + ".open", create=True)
def test_import_package(self, mock_open):
self.clients("murano").packages.create.return_value = (
"created_foo_package"
)
scenario = utils.MuranoScenario()
mock_open.return_value = "opened_foo_package.zip"
imp_package = scenario._import_package("foo_package.zip")
self.assertEqual("created_foo_package", imp_package)
self.clients("murano").packages.create.assert_called_once_with(
{}, {"file": "opened_foo_package.zip"})
mock_open.assert_called_once_with("foo_package.zip")
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.import_package")
def test_delete_package(self):
package = mock.Mock(id="package_id")
scenario = utils.MuranoScenario()
scenario._delete_package(package)
self.clients("murano").packages.delete.assert_called_once_with(
"package_id"
)
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.delete_package")
def test_update_package(self):
package = mock.Mock(id="package_id")
self.clients("murano").packages.update.return_value = "updated_package"
scenario = utils.MuranoScenario()
upd_package = scenario._update_package(
package, {"tags": ["tag"]}, "add"
)
self.assertEqual("updated_package", upd_package)
self.clients("murano").packages.update.assert_called_once_with(
"package_id",
{"tags": ["tag"]},
"add"
)
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.update_package")
def test_filter_packages(self):
self.clients("murano").packages.filter.return_value = []
scenario = utils.MuranoScenario()
return_apps_list = scenario._filter_applications(
{"category": "Web"}
)
self.assertEqual([], return_apps_list)
self.clients("murano").packages.filter.assert_called_once_with(
category="Web"
)
self._test_atomic_action_timer(scenario.atomic_actions(),
"murano.filter_applications")