Merge "Reorganize Openstack context plugins"

This commit is contained in:
Jenkins 2015-07-20 16:42:16 +00:00 committed by Gerrit Code Review
commit 2069982215
40 changed files with 2997 additions and 0 deletions

View File

@ -0,0 +1,92 @@
# 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally.plugins.openstack.scenarios.ceilometer import utils as ceilo_utils
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="ceilometer", order=450)
class CeilometerSampleGenerator(context.Context):
"""Context for creating samples and collecting resources for benchmarks."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"counter_name": {
"type": "string"
},
"counter_type": {
"type": "string"
},
"counter_unit": {
"type": "string"
},
"counter_volume": {
"type": "number",
"minimum": 0
},
"resources_per_tenant": {
"type": "integer",
"minimum": 1
},
"samples_per_resource": {
"type": "integer",
"minimum": 1
},
},
"required": ["counter_name", "counter_type", "counter_unit",
"counter_volume"],
"additionalProperties": False
}
DEFAULT_CONFIG = {
"resources_per_tenant": 5,
"samples_per_resource": 5
}
@rutils.log_task_wrapper(LOG.info, _("Enter context: `Ceilometer`"))
def setup(self):
counter_name = self.config["counter_name"]
counter_type = self.config["counter_type"]
counter_unit = self.config["counter_unit"]
counter_volume = self.config["counter_volume"]
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
self.context["tenants"][tenant_id]["samples"] = []
self.context["tenants"][tenant_id]["resources"] = []
scenario = ceilo_utils.CeilometerScenario({"user": user})
for i in range(self.config["resources_per_tenant"]):
for j in range(self.config["samples_per_resource"]):
sample = scenario._create_sample(counter_name,
counter_type,
counter_unit,
counter_volume)
self.context["tenants"][tenant_id]["samples"].append(
sample[0].to_dict())
self.context["tenants"][tenant_id]["resources"].append(
sample[0].resource_id)
@rutils.log_task_wrapper(LOG.info, _("Exit context: `Ceilometer`"))
def cleanup(self):
# We don't have API for removal of samples and resources
pass

View File

@ -0,0 +1,72 @@
# 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.plugins.openstack.scenarios.cinder import utils as cinder_utils
from rally.task import context
from rally.task.scenarios import base as scenario_base
LOG = logging.getLogger(__name__)
@context.context(name="volumes", order=420)
class VolumeGenerator(context.Context):
"""Context class for adding volumes to each user for benchmarks."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"size": {
"type": "integer",
"minimum": 1
},
"volumes_per_tenant": {
"type": "integer",
"minimum": 1
}
},
"required": ["size"],
"additionalProperties": False
}
DEFAULT_CONFIG = {
"volumes_per_tenant": 1
}
@rutils.log_task_wrapper(LOG.info, _("Enter context: `Volumes`"))
def setup(self):
size = self.config["size"]
volumes_per_tenant = self.config["volumes_per_tenant"]
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
self.context["tenants"][tenant_id].setdefault("volumes", [])
cinder_util = cinder_utils.CinderScenario({"user": user})
for i in range(volumes_per_tenant):
rnd_name = scenario_base.Scenario._generate_random_name(
prefix="ctx_rally_volume_")
vol = cinder_util._create_volume(size, display_name=rnd_name)
self.context["tenants"][tenant_id]["volumes"].append(vol._info)
@rutils.log_task_wrapper(LOG.info, _("Exit context: `Volumes`"))
def cleanup(self):
# TODO(boris-42): Delete only resources created by this context
resource_manager.cleanup(names=["cinder.volumes"],
users=self.context.get("users", []))

View File

@ -0,0 +1,102 @@
# 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.plugins.openstack.scenarios.glance import utils as glance_utils
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="images", order=410)
class ImageGenerator(context.Context):
"""Context class for adding images to each user for benchmarks."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"image_url": {
"type": "string",
},
"image_type": {
"enum": ["qcow2", "raw", "vhd", "vmdk", "vdi", "iso", "aki",
"ari", "ami"],
},
"image_container": {
"type": "string",
},
"image_name": {
"type": "string",
},
"min_ram": { # megabytes
"type": "integer",
"minimum": 0
},
"min_disk": { # gigabytes
"type": "integer",
"minimum": 0
},
"images_per_tenant": {
"type": "integer",
"minimum": 1
},
},
"required": ["image_url", "image_type", "image_container",
"images_per_tenant"],
"additionalProperties": False
}
def __init__(self, ctx):
super(ImageGenerator, self).__init__(ctx)
@rutils.log_task_wrapper(LOG.info, _("Enter context: `Images`"))
def setup(self):
image_url = self.config["image_url"]
image_type = self.config["image_type"]
image_container = self.config["image_container"]
images_per_tenant = self.config["images_per_tenant"]
image_name = self.config.get("image_name")
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
current_images = []
glance_scenario = glance_utils.GlanceScenario({"user": user})
for i in range(images_per_tenant):
if image_name and i > 0:
cur_name = image_name + str(i)
elif image_name:
cur_name = image_name
else:
cur_name = None
image = glance_scenario._create_image(
image_container, image_url, image_type,
name=cur_name, prefix="rally_ctx_image_",
min_ram=self.config.get("min_ram", 0),
min_disk=self.config.get("min_disk", 0))
current_images.append(image.id)
self.context["tenants"][tenant_id]["images"] = current_images
@rutils.log_task_wrapper(LOG.info, _("Exit context: `Images`"))
def cleanup(self):
# TODO(boris-42): Delete only resources created by this context
resource_manager.cleanup(names=["glance.images"],
users=self.context.get("users", []))

View File

@ -0,0 +1,88 @@
# 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.plugins.openstack.scenarios.heat import utils as heat_utils
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="stacks", order=435)
class StackGenerator(context.Context):
"""Context class for create temporary stacks with resources.
Stack generator allows to generate arbitrary number of stacks for
each tenant before test scenarios. In addition, it allows to define
number of resources (namely OS::Heat::RandomString) that will be created
inside each stack. After test execution the stacks will be
automatically removed from heat.
"""
# The schema of the context configuration format
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"stacks_per_tenant": {
"type": "integer",
"minimum": 1
},
"resources_per_stack": {
"type": "integer",
"minimum": 1
}
},
"additionalProperties": False
}
DEFAULT_CONFIG = {
"stacks_per_tenant": 2,
"resources_per_stack": 10
}
@staticmethod
def _prepare_stack_template(res_num):
template = {
"heat_template_version": "2014-10-16",
"description": "Test template for rally",
"resources": {}
}
rand_string = {"type": "OS::Heat::RandomString"}
for i in range(res_num):
template["resources"]["TestResource%d" % i] = rand_string
return template
@rutils.log_task_wrapper(LOG.info, _("Enter context: `Stacks`"))
def setup(self):
template = self._prepare_stack_template(
self.config["resources_per_stack"])
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
heat_scenario = heat_utils.HeatScenario({"user": user})
self.context["tenants"][tenant_id]["stacks"] = []
for i in range(self.config["stacks_per_tenant"]):
stack = heat_scenario._create_stack(template)
self.context["tenants"][tenant_id]["stacks"].append(stack.id)
@rutils.log_task_wrapper(LOG.info, _("Exit context: `Stacks`"))
def cleanup(self):
resource_manager.cleanup(names=["heat.stacks"],
users=self.context.get("users", []))

View File

@ -0,0 +1,94 @@
# Copyright 2014: 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally import exceptions
from rally import osclients
from rally.plugins.openstack.wrappers import keystone
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="roles", order=330)
class RoleGenerator(context.Context):
"""Context class for adding temporary roles for benchmarks."""
CONFIG_SCHEMA = {
"type": "array",
"$schema": consts.JSON_SCHEMA,
"items": {
"type": "string",
},
"additionalProperties": False
}
def __init__(self, ctx):
super(RoleGenerator, self).__init__(ctx)
self.context["roles"] = []
self.endpoint = self.context["admin"]["endpoint"]
def _add_role(self, admin_endpoint, context_role):
"""Add role to users.
:param admin_endpoint: The base url.
:param context_role: name of existing role.
"""
client = keystone.wrap(osclients.Clients(admin_endpoint).keystone())
default_roles = client.list_roles()
for def_role in default_roles:
if str(def_role.name) == context_role:
role = def_role
break
else:
raise exceptions.NoSuchRole(role=context_role)
LOG.debug("Adding role %s to all users" % (role.id))
for user in self.context["users"]:
client.add_role(user_id=user["id"], role_id=role.id,
project_id=user["tenant_id"])
return {"id": str(role.id), "name": str(role.name)}
def _remove_role(self, admin_endpoint, role):
"""Remove given role from users.
:param admin_endpoint: The base url.
:param role: dictionary with role parameters (id, name).
"""
client = keystone.wrap(osclients.Clients(admin_endpoint).keystone())
for user in self.context["users"]:
with logging.ExceptionLogger(
LOG, _("Failed to remove role: %s") % role["id"]):
client.remove_role(
user_id=user["id"], role_id=role["id"],
project_id=user["tenant_id"])
@rutils.log_task_wrapper(LOG.info, _("Enter context: `roles`"))
def setup(self):
"""Add roles to all users."""
for name in self.config:
role = self._add_role(self.endpoint, name)
self.context["roles"].append(role)
@rutils.log_task_wrapper(LOG.info, _("Exit context: `roles`"))
def cleanup(self):
"""Remove roles from users."""
for role in self.context["roles"]:
self._remove_role(self.endpoint, role)

View File

@ -0,0 +1,282 @@
# Copyright 2014: 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 collections
import uuid
from oslo_config import cfg
from rally.common import broker
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally import exceptions
from rally import objects
from rally import osclients
from rally.plugins.openstack.wrappers import keystone
from rally.plugins.openstack.wrappers import network
from rally.task import context
from rally.task import utils
LOG = logging.getLogger(__name__)
USER_CONTEXT_OPTS = [
cfg.IntOpt("resource_management_workers",
default=30,
help="How many concurrent threads use for serving users "
"context"),
cfg.StrOpt("project_domain",
default="default",
help="ID of domain in which projects will be created."),
cfg.StrOpt("user_domain",
default="default",
help="ID of domain in which users will be created."),
]
CONF = cfg.CONF
CONF.register_opts(USER_CONTEXT_OPTS,
group=cfg.OptGroup(name="users_context",
title="benchmark context options"))
@context.context(name="users", order=100)
class UserGenerator(context.Context):
"""Context class for generating temporary users/tenants for benchmarks."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"tenants": {
"type": "integer",
"minimum": 1
},
"users_per_tenant": {
"type": "integer",
"minimum": 1
},
"resource_management_workers": {
"type": "integer",
"minimum": 1
},
"project_domain": {
"type": "string",
},
"user_domain": {
"type": "string",
},
},
"additionalProperties": False
}
PATTERN_TENANT = "ctx_rally_%(task_id)s_tenant_%(iter)i"
PATTERN_USER = "ctx_rally_%(tenant_id)s_user_%(uid)d"
DEFAULT_CONFIG = {
"tenants": 1,
"users_per_tenant": 1,
"resource_management_workers":
cfg.CONF.users_context.resource_management_workers,
"project_domain": cfg.CONF.users_context.project_domain,
"user_domain": cfg.CONF.users_context.user_domain
}
def __init__(self, context):
super(UserGenerator, self).__init__(context)
self.context["users"] = []
self.context["tenants"] = {}
self.endpoint = self.context["admin"]["endpoint"]
# NOTE(boris-42): I think this is the best place for adding logic when
# we are using pre created users or temporary. So we
# should rename this class s/UserGenerator/UserContext/
# and change a bit logic of populating lists of users
# and tenants
def _remove_default_security_group(self):
"""Delete default security group for tenants."""
clients = osclients.Clients(self.endpoint)
if consts.Service.NEUTRON not in clients.services().values():
return
use_sg, msg = network.wrap(clients).supports_security_group()
if not use_sg:
LOG.debug("Security group context is disabled: %s" % msg)
return
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
with logging.ExceptionLogger(
LOG, _("Unable to delete default security group")):
uclients = osclients.Clients(user["endpoint"])
sg = uclients.nova().security_groups.find(name="default")
clients.neutron().delete_security_group(sg.id)
def _remove_associated_networks(self):
"""Delete associated Nova networks from tenants."""
# NOTE(rmk): Ugly hack to deal with the fact that Nova Network
# networks can only be disassociated in an admin context. Discussed
# with boris-42 before taking this approach [LP-Bug #1350517].
clients = osclients.Clients(self.endpoint)
if consts.Service.NOVA not in clients.services().values():
return
nova_admin = clients.nova()
if not utils.check_service_status(nova_admin, "nova-network"):
return
for net in nova_admin.networks.list():
network_tenant_id = nova_admin.networks.get(net).project_id
if network_tenant_id in self.context["tenants"]:
try:
nova_admin.networks.disassociate(net)
except Exception as ex:
LOG.warning("Failed disassociate net: %(tenant_id)s. "
"Exception: %(ex)s" %
{"tenant_id": network_tenant_id, "ex": ex})
def _create_tenants(self):
threads = self.config["resource_management_workers"]
tenants = collections.deque()
def publish(queue):
for i in range(self.config["tenants"]):
args = (self.config["project_domain"], self.task["uuid"], i)
queue.append(args)
def consume(cache, args):
domain, task_id, i = args
if "client" not in cache:
clients = osclients.Clients(self.endpoint)
cache["client"] = keystone.wrap(clients.keystone())
tenant = cache["client"].create_project(
self.PATTERN_TENANT % {"task_id": task_id, "iter": i}, domain)
tenant_dict = {"id": tenant.id, "name": tenant.name}
tenants.append(tenant_dict)
# NOTE(msdubov): consume() will fill the tenants list in the closure.
broker.run(publish, consume, threads)
tenants_dict = {}
for t in tenants:
tenants_dict[t["id"]] = t
return tenants_dict
def _create_users(self):
# NOTE(msdubov): This should be called after _create_tenants().
threads = self.config["resource_management_workers"]
users_per_tenant = self.config["users_per_tenant"]
users = collections.deque()
def publish(queue):
for tenant_id in self.context["tenants"]:
for user_id in range(users_per_tenant):
username = self.PATTERN_USER % {"tenant_id": tenant_id,
"uid": user_id}
password = str(uuid.uuid4())
args = (username, password, self.config["project_domain"],
self.config["user_domain"], tenant_id)
queue.append(args)
def consume(cache, args):
username, password, project_dom, user_dom, tenant_id = args
if "client" not in cache:
clients = osclients.Clients(self.endpoint)
cache["client"] = keystone.wrap(clients.keystone())
client = cache["client"]
user = client.create_user(username, password,
"%s@email.me" % username,
tenant_id, user_dom)
user_endpoint = objects.Endpoint(
client.auth_url, user.name, password,
self.context["tenants"][tenant_id]["name"],
consts.EndpointPermission.USER, client.region_name,
project_domain_name=project_dom, user_domain_name=user_dom,
endpoint_type=self.endpoint.endpoint_type)
users.append({"id": user.id,
"endpoint": user_endpoint,
"tenant_id": tenant_id})
# NOTE(msdubov): consume() will fill the users list in the closure.
broker.run(publish, consume, threads)
return list(users)
def _delete_tenants(self):
threads = self.config["resource_management_workers"]
self._remove_associated_networks()
def publish(queue):
for tenant_id in self.context["tenants"]:
queue.append(tenant_id)
def consume(cache, tenant_id):
if "client" not in cache:
clients = osclients.Clients(self.endpoint)
cache["client"] = keystone.wrap(clients.keystone())
cache["client"].delete_project(tenant_id)
broker.run(publish, consume, threads)
self.context["tenants"] = {}
def _delete_users(self):
threads = self.config["resource_management_workers"]
def publish(queue):
for user in self.context["users"]:
queue.append(user["id"])
def consume(cache, user_id):
if "client" not in cache:
clients = osclients.Clients(self.endpoint)
cache["client"] = keystone.wrap(clients.keystone())
cache["client"].delete_user(user_id)
broker.run(publish, consume, threads)
self.context["users"] = []
@rutils.log_task_wrapper(LOG.info, _("Enter context: `users`"))
def setup(self):
"""Create tenants and users, using the broker pattern."""
threads = self.config["resource_management_workers"]
LOG.debug("Creating %(tenants)d tenants using %(threads)s threads" %
{"tenants": self.config["tenants"], "threads": threads})
self.context["tenants"] = self._create_tenants()
if len(self.context["tenants"]) < self.config["tenants"]:
raise exceptions.ContextSetupFailure(
ctx_name=self.get_name(),
msg=_("Failed to create the requested number of tenants."))
users_num = self.config["users_per_tenant"] * self.config["tenants"]
LOG.debug("Creating %(users)d users using %(threads)s threads" %
{"users": users_num, "threads": threads})
self.context["users"] = self._create_users()
if len(self.context["users"]) < users_num:
raise exceptions.ContextSetupFailure(
ctx_name=self.get_name(),
msg=_("Failed to create the requested number of users."))
@rutils.log_task_wrapper(LOG.info, _("Exit context: `users`"))
def cleanup(self):
"""Delete tenants and users, using the broker pattern."""
self._remove_default_security_group()
self._delete_users()
self._delete_tenants()

View File

@ -0,0 +1,67 @@
# 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 zipfile
from rally.common.i18n import _
from rally.common.i18n import _LE
from rally.common import log as logging
from rally.common import utils
from rally import consts
from rally import osclients
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="murano_packages", order=401)
class PackageGenerator(context.Context):
"""Context class for uploading applications for murano."""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"app_package": {
"type": "string",
}
},
"required": ["app_package"],
"additionalProperties": False
}
@utils.log_task_wrapper(LOG.info, _("Enter context: `Murano packages`"))
def setup(self):
if not zipfile.is_zipfile(self.config["app_package"]):
msg = (_LE("There is no zip archive by this path: %s")
% self.config["app_package"])
raise OSError(msg)
for user, tenant_id in utils.iterate_per_tenants(
self.context["users"]):
clients = osclients.Clients(user["endpoint"])
self.context["tenants"][tenant_id]["packages"] = []
package = clients.murano().packages.create(
{"categories": ["Web"], "tags": ["tag"]},
{"file": open(self.config["app_package"])})
self.context["tenants"][tenant_id]["packages"].append(package)
@utils.log_task_wrapper(LOG.info, _("Exit context: `Murano packages`"))
def cleanup(self):
resource_manager.cleanup(names=["murano.packages"],
users=self.context.get("users", []))

View File

@ -0,0 +1,120 @@
# Copyright 2013: 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 six
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils
from rally import osclients
from rally.plugins.openstack.wrappers import network
from rally.task import context
LOG = logging.getLogger(__name__)
SSH_GROUP_NAME = "rally_ssh_open"
def _prepare_open_secgroup(endpoint, secgroup_name):
"""Generate secgroup allowing all tcp/udp/icmp access.
In order to run tests on instances it is necessary to have SSH access.
This function generates a secgroup which allows all tcp/udp/icmp access.
:param endpoint: clients endpoint
:param secgroup_name: security group name
:returns: dict with security group details
"""
nova = osclients.Clients(endpoint).nova()
if secgroup_name not in [sg.name for sg in nova.security_groups.list()]:
descr = "Allow ssh access to VMs created by Rally for benchmarking"
rally_open = nova.security_groups.create(secgroup_name, descr)
rally_open = nova.security_groups.find(name=secgroup_name)
rules_to_add = [
{
"ip_protocol": "tcp",
"to_port": 65535,
"from_port": 1,
"ip_range": {"cidr": "0.0.0.0/0"}
},
{
"ip_protocol": "udp",
"to_port": 65535,
"from_port": 1,
"ip_range": {"cidr": "0.0.0.0/0"}
},
{
"ip_protocol": "icmp",
"to_port": -1,
"from_port": -1,
"ip_range": {"cidr": "0.0.0.0/0"}
}
]
def rule_match(criteria, existing_rule):
return all(existing_rule[key] == value
for key, value in six.iteritems(criteria))
for new_rule in rules_to_add:
if not any(rule_match(new_rule, existing_rule) for existing_rule
in rally_open.rules):
nova.security_group_rules.create(
rally_open.id,
from_port=new_rule["from_port"],
to_port=new_rule["to_port"],
ip_protocol=new_rule["ip_protocol"],
cidr=new_rule["ip_range"]["cidr"])
return rally_open.to_dict()
@context.context(name="allow_ssh", order=320)
class AllowSSH(context.Context):
@utils.log_task_wrapper(LOG.info, _("Enter context: `allow_ssh`"))
def setup(self):
admin_or_user = (self.context.get("admin") or
self.context.get("users")[0])
net_wrapper = network.wrap(
osclients.Clients(admin_or_user["endpoint"]),
self.config)
use_sg, msg = net_wrapper.supports_security_group()
if not use_sg:
LOG.info(_("Security group context is disabled: %s") % msg)
return
secgroup_name = "%s_%s" % (SSH_GROUP_NAME,
self.context["task"]["uuid"])
for user in self.context["users"]:
user["secgroup"] = _prepare_open_secgroup(user["endpoint"],
secgroup_name)
@utils.log_task_wrapper(LOG.info, _("Exit context: `allow_ssh`"))
def cleanup(self):
for user, tenant_id in utils.iterate_per_tenants(
self.context["users"]):
with logging.ExceptionLogger(
LOG, _("Unable to delete secgroup: %s.") %
user["secgroup"]["name"]):
clients = osclients.Clients(user["endpoint"])
clients.nova().security_groups.get(
user["secgroup"]["id"]).delete()

View File

@ -0,0 +1,80 @@
# Copyright 2014: 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 six
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils
from rally import consts
from rally import osclients
from rally.plugins.openstack.wrappers import network as network_wrapper
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="network", order=350)
class Network(context.Context):
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"start_cidr": {
"type": "string"
},
"networks_per_tenant": {
"type": "integer",
"minimum": 1
}
},
"additionalProperties": False
}
DEFAULT_CONFIG = {
"start_cidr": "10.2.0.0/24",
"networks_per_tenant": 1
}
def __init__(self, ctx):
super(Network, self).__init__(ctx)
self.net_wrapper = network_wrapper.wrap(
osclients.Clients(ctx["admin"]["endpoint"]),
self.config)
@utils.log_task_wrapper(LOG.info, _("Enter context: `network`"))
def setup(self):
for user, tenant_id in (utils.iterate_per_tenants(
self.context.get("users", []))):
self.context["tenants"][tenant_id]["networks"] = []
for i in range(self.config["networks_per_tenant"]):
# NOTE(amaretskiy): add_router and subnets_num take effect
# for Neutron only.
# NOTE(amaretskiy): Do we need neutron subnets_num > 1 ?
network = self.net_wrapper.create_network(tenant_id,
add_router=True,
subnets_num=1)
self.context["tenants"][tenant_id]["networks"].append(network)
@utils.log_task_wrapper(LOG.info, _("Exit context: `network`"))
def cleanup(self):
for tenant_id, tenant_ctx in six.iteritems(self.context["tenants"]):
for network in tenant_ctx.get("networks", []):
with logging.ExceptionLogger(
LOG,
_("Failed to delete network for tenant %s")
% tenant_id):
self.net_wrapper.delete_network(network)

View File

@ -0,0 +1,131 @@
# Copyright 2014: 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.
from novaclient import exceptions as nova_exceptions
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally import osclients
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="flavors", order=340)
class FlavorsGenerator(context.Context):
"""Context creates a list of flavors."""
CONFIG_SCHEMA = {
"type": "array",
"$schema": consts.JSON_SCHEMA,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
},
"ram": {
"type": "integer",
"minimum": 1
},
"vcpus": {
"type": "integer",
"minimum": 1
},
"disk": {
"type": "integer",
"minimum": 0
},
"swap": {
"type": "integer",
"minimum": 0
},
"ephemeral": {
"type": "integer",
"minimum": 0
},
"extra_specs": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": False,
"required": ["name", "ram"]
}
}
@rutils.log_task_wrapper(LOG.info, _("Enter context: `flavors`"))
def setup(self):
"""Create list of flavors."""
self.context["flavors"] = {}
clients = osclients.Clients(self.context["admin"]["endpoint"])
for flavor_config in self.config:
extra_specs = flavor_config.get("extra_specs")
flavor_config = FlavorConfig(**flavor_config)
try:
flavor = clients.nova().flavors.create(**flavor_config)
except nova_exceptions.Conflict as e:
LOG.warning("Using already existing flavor %s" %
flavor_config["name"])
if logging.is_debug():
LOG.exception(e)
continue
if extra_specs:
flavor.set_keys(extra_specs)
self.context["flavors"][flavor_config["name"]] = flavor.to_dict()
LOG.debug("Created flavor with id '%s'" % flavor.id)
@rutils.log_task_wrapper(LOG.info, _("Exit context: `flavors`"))
def cleanup(self):
"""Delete created flavors."""
clients = osclients.Clients(self.context["admin"]["endpoint"])
for flavor in self.context["flavors"].values():
with logging.ExceptionLogger(
LOG, _("Can't delete flavor %s") % flavor["id"]):
rutils.retry(3, clients.nova().flavors.delete, flavor["id"])
LOG.debug("Flavor is deleted %s" % flavor["id"])
class FlavorConfig(dict):
def __init__(self, name, ram, vcpus=1, disk=0, swap=0, ephemeral=0,
extra_specs=None):
"""Flavor configuration for context and flavor & image validation code.
Context code uses this code to provide default values for flavor
creation. Validation code uses this class as a Flavor instance to
check image validity against a flavor that is to be created by
the context.
:param name: name of the newly created flavor
:param ram: RAM amount for the flavor (MBs)
:param vcpus: VCPUs amount for the flavor
:param disk: disk amount for the flavor (GBs)
:param swap: swap amount for the flavor (MBs)
:param ephemeral: ephemeral disk amount for the flavor (GBs)
:param extra_specs: is ignored
"""
super(FlavorConfig, self).__init__(
name=name, ram=ram, vcpus=vcpus, disk=disk,
swap=swap, ephemeral=ephemeral)
self.__dict__.update(self)

View File

@ -0,0 +1,61 @@
# Copyright 2014: Rackspace UK
# 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 novaclient.exceptions
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils
from rally import osclients
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.task import context
LOG = logging.getLogger(__name__)
@context.context(name="keypair", order=310)
class Keypair(context.Context):
KEYPAIR_NAME = "rally_ssh_key"
def _generate_keypair(self, endpoint):
keypair_name = "%s_%s" % (
self.KEYPAIR_NAME, self.context["task"]["uuid"])
nova_client = osclients.Clients(endpoint).nova()
# NOTE(hughsaunders): If keypair exists, it must be deleted as we can't
# retrieve the private key
try:
nova_client.keypairs.delete(keypair_name)
except novaclient.exceptions.NotFound:
pass
keypair = nova_client.keypairs.create(keypair_name)
return {"private": keypair.private_key,
"public": keypair.public_key,
"name": keypair_name,
"id": keypair.id}
@utils.log_task_wrapper(LOG.info, _("Enter context: `keypair`"))
def setup(self):
for user in self.context["users"]:
user["keypair"] = self._generate_keypair(user["endpoint"])
@utils.log_task_wrapper(LOG.info, _("Exit context: `keypair`"))
def cleanup(self):
# TODO(boris-42): Delete only resources created by this context
resource_manager.cleanup(names=["nova.keypairs"],
users=self.context.get("users", []))

View File

@ -0,0 +1,108 @@
# 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.
from rally.common.i18n import _
from rally.common import log as logging
from rally.common import utils as rutils
from rally import consts
from rally import osclients
from rally.plugins.openstack.context.cleanup import manager as resource_manager
from rally.plugins.openstack.scenarios.nova import utils as nova_utils
from rally.task import context
from rally.task import types
LOG = logging.getLogger(__name__)
@context.context(name="servers", order=430)
class ServerGenerator(context.Context):
"""Context class for adding temporary servers for benchmarks.
Servers are added for each tenant.
"""
CONFIG_SCHEMA = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
"properties": {
"image": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"flavor": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"servers_per_tenant": {
"type": "integer",
"minimum": 1
},
},
"required": ["image", "flavor"],
"additionalProperties": False
}
DEFAULT_CONFIG = {
"servers_per_tenant": 5
}
@rutils.log_task_wrapper(LOG.info, _("Enter context: `Servers`"))
def setup(self):
image = self.config["image"]
flavor = self.config["flavor"]
servers_per_tenant = self.config["servers_per_tenant"]
clients = osclients.Clients(self.context["users"][0]["endpoint"])
image_id = types.ImageResourceType.transform(clients=clients,
resource_config=image)
flavor_id = types.FlavorResourceType.transform(clients=clients,
resource_config=flavor)
for user, tenant_id in rutils.iterate_per_tenants(
self.context["users"]):
LOG.debug("Booting servers for user tenant %s "
% (user["tenant_id"]))
nova_scenario = nova_utils.NovaScenario({"user": user})
LOG.debug("Calling _boot_servers with image_id=%(image_id)s "
"flavor_id=%(flavor_id)s "
"servers_per_tenant=%(servers_per_tenant)s"
% {"image_id": image_id,
"flavor_id": flavor_id,
"servers_per_tenant": servers_per_tenant})
servers = nova_scenario._boot_servers(image_id, flavor_id,
servers_per_tenant)
current_servers = [server.id for server in servers]
LOG.debug("Adding booted servers %s to context"
% current_servers)
self.context["tenants"][tenant_id][
"servers"] = current_servers
@rutils.log_task_wrapper(LOG.info, _("Exit context: `Servers`"))
def cleanup(self):
resource_manager.cleanup(names=["nova.servers"],
users=self.context.get("users", []))

View File

@ -0,0 +1,124 @@
# 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 copy
import mock
from rally.plugins.openstack.context.ceilometer import samples
from tests.unit import test
CTX = "rally.plugins.openstack.context.ceilometer"
class CeilometerSampleGeneratorTestCase(test.TestCase):
def _gen_tenants(self, count):
tenants = {}
for id_ in range(count):
tenants[str(id_)] = {"name": str(id_)}
return tenants
def _gen_context(self, tenants_count, users_per_tenant,
resources_per_tenant, samples_per_resource):
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants.keys():
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": mock.MagicMock()})
context = {
"config": {
"users": {
"tenants": tenants_count,
"users_per_tenant": users_per_tenant,
"concurrent": 10,
},
"ceilometer": {
"counter_name": "fake-counter-name",
"counter_type": "fake-counter-type",
"counter_unit": "fake-counter-unit",
"counter_volume": 100,
"resources_per_tenant": resources_per_tenant,
"samples_per_resource": samples_per_resource
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
return tenants, context
def test_init(self):
context = {}
context["task"] = mock.MagicMock()
context["config"] = {
"ceilometer": {
"counter_name": "cpu_util",
"counter_type": "gauge",
"counter_unit": "instance",
"counter_volume": 1.0,
"resources_per_tenant": 5,
"samples_per_resource": 5
}
}
inst = samples.CeilometerSampleGenerator(context)
self.assertEqual(inst.config, context["config"]["ceilometer"])
@mock.patch("%s.samples.ceilo_utils.CeilometerScenario._create_sample"
% CTX)
def test_setup(self, mock_ceilometer_scenario__create_sample):
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)
sample = {
"counter_name": "fake-counter-name",
"counter_type": "fake-counter-type",
"counter_unit": "fake-counter-unit",
"counter_volume": 100,
"resource_id": "fake-resource-id"
}
new_context = copy.deepcopy(real_context)
for id_ in tenants.keys():
new_context["tenants"][id_].setdefault("samples", [])
new_context["tenants"][id_].setdefault("resources", [])
for i in range(resources_per_tenant):
for j in range(samples_per_resource):
new_context["tenants"][id_]["samples"].append(sample)
new_context["tenants"][id_]["resources"].append(
sample["resource_id"])
mock_ceilometer_scenario__create_sample.return_value = [
mock.MagicMock(to_dict=lambda: sample, **sample)]
ceilometer_ctx = samples.CeilometerSampleGenerator(real_context)
ceilometer_ctx.setup()
self.assertEqual(new_context, ceilometer_ctx.context)
def test_cleanup(self):
tenants, context = self._gen_context(2, 5, 3, 3)
ceilometer_ctx = samples.CeilometerSampleGenerator(context)
ceilometer_ctx.cleanup()

View File

@ -0,0 +1,134 @@
# 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 copy
import mock
from rally.plugins.openstack.context.cinder import volumes
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context"
SCN = "rally.plugins.openstack.scenarios"
class VolumeGeneratorTestCase(test.ScenarioTestCase):
def _gen_tenants(self, count):
tenants = {}
for id_ in range(count):
tenants[str(id_)] = {"name": str(id_)}
return tenants
def test_init(self):
context = {}
context["task"] = mock.MagicMock()
context["config"] = {
"volumes": {
"size": 1,
"volumes_per_tenant": 5,
}
}
inst = volumes.VolumeGenerator(context)
self.assertEqual(inst.config, context["config"]["volumes"])
@mock.patch("%s.cinder.utils.CinderScenario._create_volume" % SCN,
return_value=fakes.FakeVolume(id="uuid"))
def test_setup(self, mock_cinder_scenario__create_volume):
tenants_count = 2
users_per_tenant = 5
volumes_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants.keys():
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": mock.MagicMock()})
real_context = {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 5,
"concurrent": 10,
},
"volumes": {
"size": 1,
"volumes_per_tenant": volumes_per_tenant,
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
new_context = copy.deepcopy(real_context)
for id_ in tenants.keys():
new_context["tenants"][id_].setdefault("volumes", [])
for i in range(volumes_per_tenant):
new_context["tenants"][id_]["volumes"].append({"id": "uuid"})
volumes_ctx = volumes.VolumeGenerator(real_context)
volumes_ctx.setup()
self.assertEqual(new_context, real_context)
@mock.patch("%s.cinder.volumes.resource_manager.cleanup" % CTX)
def test_cleanup(self, mock_cleanup):
tenants_count = 2
users_per_tenant = 5
volumes_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants.keys():
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": "endpoint"})
tenants[id_].setdefault("volumes", [])
for j in range(volumes_per_tenant):
tenants[id_]["volumes"].append({"id": "uuid"})
context = {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 5,
"concurrent": 10,
},
"volumes": {
"size": 1,
"volumes_per_tenant": 5,
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
volumes_ctx = volumes.VolumeGenerator(context)
volumes_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["cinder.volumes"],
users=context["users"])

View File

@ -0,0 +1,163 @@
# 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 copy
import jsonschema
import mock
from rally.plugins.openstack.context.glance import images
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context.glance"
SCN = "rally.plugins.openstack.scenarios.glance"
class ImageGeneratorTestCase(test.ScenarioTestCase):
def _gen_tenants(self, count):
tenants = {}
for id_ in range(count):
tenants[str(id_)] = {"name": str(id_)}
return tenants
@mock.patch("%s.images.context.Context.__init__" % CTX)
def test_init(self, mock_context___init__):
context = {}
context["task"] = mock.MagicMock()
context["config"] = {
"images": {
"image_url": "mock_url",
"image_type": "qcow2",
"image_container": "bare",
"images_per_tenant": 4,
"image_name": "some_name",
"min_ram": 128,
"min_disk": 1,
}
}
images.ImageGenerator(context)
mock_context___init__.assert_called_once_with(context)
def test_init_validation(self):
context = {}
context["task"] = mock.MagicMock()
context["config"] = {
"images": {
"image_url": "mock_url"
}
}
self.assertRaises(jsonschema.ValidationError,
images.ImageGenerator.validate, context)
@mock.patch("%s.utils.GlanceScenario._create_image" % SCN,
return_value=fakes.FakeImage(id="uuid"))
def test_setup(self, mock_glance_scenario__create_image):
tenants_count = 2
users_per_tenant = 5
images_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants:
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": mock.MagicMock()})
real_context = {
"config": {
"users": {
"tenants": tenants_count,
"users_per_tenant": users_per_tenant,
"concurrent": 10,
},
"images": {
"image_url": "mock_url",
"image_type": "qcow2",
"image_container": "bare",
"images_per_tenant": images_per_tenant,
"image_name": "some_name",
"min_ram": 128,
"min_disk": 1,
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
new_context = copy.deepcopy(real_context)
for id_ in new_context["tenants"].keys():
new_context["tenants"][id_].setdefault("images", [])
for j in range(images_per_tenant):
new_context["tenants"][id_]["images"].append("uuid")
images_ctx = images.ImageGenerator(real_context)
images_ctx.setup()
self.assertEqual(new_context, real_context)
@mock.patch("%s.images.resource_manager.cleanup" % CTX)
def test_cleanup(self, mock_cleanup):
tenants_count = 2
users_per_tenant = 5
images_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants:
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": "endpoint"})
tenants[id_].setdefault("images", [])
for j in range(images_per_tenant):
tenants[id_]["images"].append("uuid")
context = {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 5,
"concurrent": 10,
},
"images": {
"image_url": "mock_url",
"image_type": "qcow2",
"image_container": "bare",
"images_per_tenant": 5,
"image_name": "some_name",
"min_ram": 128,
"min_disk": 1,
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
images_ctx = images.ImageGenerator(context)
images_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["glance.images"],
users=context["users"])

View File

@ -0,0 +1,97 @@
# 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.context.heat import stacks
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context"
SCN = "rally.plugins.openstack.scenarios"
class TestStackGenerator(test.ScenarioTestCase):
def _gen_tenants(self, count):
tenants = {}
for id_ in range(count):
tenants[str(id_)] = dict(name=str(id_))
return tenants
def test_init(self):
context = {
"task": mock.MagicMock(),
"config": {
"stacks": {
"stacks_per_tenant": 1,
"resources_per_stack": 1
}
}
}
inst = stacks.StackGenerator(context)
self.assertEqual(inst.config, context["config"]["stacks"])
@mock.patch("%s.heat.utils.HeatScenario._create_stack" % SCN,
return_value=fakes.FakeStack(id="uuid"))
def test_setup(self, mock_heat_scenario__create_stack):
tenants_count = 2
users_per_tenant = 5
stacks_per_tenant = 1
tenants = self._gen_tenants(tenants_count)
users = []
for ten_id in tenants:
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": ten_id,
"endpoint": mock.MagicMock()})
context = {
"config": {
"users": {
"tenants": tenants_count,
"users_per_tenant": users_per_tenant,
"concurrent": 10,
},
"stacks": {
"stacks_per_tenant": stacks_per_tenant,
"resources_per_stack": 1
}
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
stack_ctx = stacks.StackGenerator(context)
stack_ctx.setup()
self.assertEqual(tenants_count * stacks_per_tenant,
mock_heat_scenario__create_stack.call_count)
# check that stack ids have been saved in context
for ten_id in context["tenants"].keys():
self.assertEqual(stacks_per_tenant,
len(context["tenants"][ten_id]["stacks"]))
@mock.patch("%s.heat.stacks.resource_manager.cleanup" % CTX)
def test_cleanup(self, mock_cleanup):
context = {
"task": mock.MagicMock(),
"users": mock.MagicMock()
}
stack_ctx = stacks.StackGenerator(context)
stack_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["heat.stacks"],
users=context["users"])

View File

@ -0,0 +1,131 @@
# Copyright 2014: 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 import exceptions
from rally.plugins.openstack.context.keystone import roles
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context.keystone.roles"
class RoleGeneratorTestCase(test.TestCase):
def create_default_roles_and_patch_add_remove_functions(self, fc):
fc.keystone().roles.add_user_role = mock.MagicMock()
fc.keystone().roles.remove_user_role = mock.MagicMock()
fc.keystone().roles.create("r1", "test_role1")
fc.keystone().roles.create("r2", "test_role2")
self.assertEqual(2, len(fc.keystone().roles.list()))
@property
def context(self):
return {
"config": {
"roles": [
"test_role1",
"test_role2"
]
},
"admin": {"endpoint": mock.MagicMock()},
"task": mock.MagicMock()
}
@mock.patch("%s.osclients" % CTX)
def test_add_role(self, mock_osclients):
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
self.create_default_roles_and_patch_add_remove_functions(fc)
ctx = roles.RoleGenerator(self.context)
ctx.context["users"] = [{"id": "u1", "tenant_id": "t1"},
{"id": "u2", "tenant_id": "t2"}]
result = ctx._add_role(mock.MagicMock(),
self.context["config"]["roles"][0])
expected = {"id": "r1", "name": "test_role1"}
self.assertEqual(expected, result)
@mock.patch("%s.osclients" % CTX)
def test_add_role_which_does_not_exist(self, mock_osclients):
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
self.create_default_roles_and_patch_add_remove_functions(fc)
ctx = roles.RoleGenerator(self.context)
ctx.context["users"] = [{"id": "u1", "tenant_id": "t1"},
{"id": "u2", "tenant_id": "t2"}]
ex = self.assertRaises(exceptions.NoSuchRole, ctx._add_role,
mock.MagicMock(), "unknown_role")
expected = "There is no role with name `unknown_role`."
self.assertEqual(expected, str(ex))
@mock.patch("%s.osclients" % CTX)
def test_remove_role(self, mock_osclients):
role = mock.MagicMock()
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
self.create_default_roles_and_patch_add_remove_functions(fc)
ctx = roles.RoleGenerator(self.context)
ctx.context["users"] = [{"id": "u1", "tenant_id": "t1"},
{"id": "u2", "tenant_id": "t2"}]
ctx._remove_role(mock.MagicMock(), role)
calls = [
mock.call("u1", role["id"], tenant="t1"),
mock.call("u2", role["id"], tenant="t2"),
]
mock_keystone = mock_osclients.Clients().keystone()
mock_keystone.roles.remove_user_role.assert_has_calls(calls)
@mock.patch("%s.osclients" % CTX)
def test_setup_and_cleanup(self, mock_osclients):
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
self.create_default_roles_and_patch_add_remove_functions(fc)
with roles.RoleGenerator(self.context) as ctx:
ctx.context["users"] = [{"id": "u1", "tenant_id": "t1"},
{"id": "u2", "tenant_id": "t2"}]
ctx.setup()
calls = [
mock.call("u1", "r1", tenant="t1"),
mock.call("u2", "r1", tenant="t2"),
mock.call("u1", "r2", tenant="t1"),
mock.call("u2", "r2", tenant="t2")
]
fc.keystone().roles.add_user_role.assert_has_calls(calls)
self.assertEqual(
4, fc.keystone().roles.add_user_role.call_count)
self.assertEqual(
0, fc.keystone().roles.remove_user_role.call_count)
self.assertEqual(2, len(ctx.context["roles"]))
self.assertEqual(2, len(fc.keystone().roles.list()))
# Cleanup (called by content manager)
self.assertEqual(2, len(fc.keystone().roles.list()))
self.assertEqual(4, fc.keystone().roles.add_user_role.call_count)
self.assertEqual(4, fc.keystone().roles.remove_user_role.call_count)
calls = [
mock.call("u1", "r1", tenant="t1"),
mock.call("u2", "r1", tenant="t2"),
mock.call("u1", "r2", tenant="t1"),
mock.call("u2", "r2", tenant="t2")
]
fc.keystone().roles.remove_user_role.assert_has_calls(calls)

View File

@ -0,0 +1,358 @@
# Copyright 2014: 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 import consts
from rally import exceptions
from rally import objects
from rally.plugins.openstack.context.keystone import users
from tests.unit import test
CTX = "rally.plugins.openstack.context.keystone.users"
class UserGeneratorTestCase(test.TestCase):
tenants_num = 1
users_per_tenant = 5
users_num = tenants_num * users_per_tenant
threads = 10
@property
def context(self):
return {
"config": {
"users": {
"tenants": self.tenants_num,
"users_per_tenant": self.users_per_tenant,
"resource_management_workers": self.threads,
}
},
"admin": {"endpoint": mock.MagicMock()},
"task": {"uuid": "task_id"}
}
def setUp(self):
super(UserGeneratorTestCase, self).setUp()
self.osclients_patcher = mock.patch("%s.osclients" % CTX)
self.osclients = self.osclients_patcher.start()
def tearDown(self):
self.osclients_patcher.stop()
super(UserGeneratorTestCase, self).tearDown()
@mock.patch("%s.network.wrap" % CTX)
def test__remove_default_security_group_not_needed(self, mock_wrap):
services = {"compute": consts.Service.NOVA}
self.osclients.Clients().services.return_value = services
user_generator = users.UserGenerator(self.context)
user_generator._remove_default_security_group()
self.assertFalse(mock_wrap.called)
@mock.patch("%s.network.wrap" % CTX)
def test__remove_default_security_group_neutron_no_sg(self, mock_wrap):
net_wrapper = mock.Mock(SERVICE_IMPL=consts.Service.NEUTRON)
net_wrapper.supports_security_group.return_value = (False, None)
mock_wrap.return_value = net_wrapper
user_generator = users.UserGenerator(self.context)
admin_clients = mock.Mock()
admin_clients.services.return_value = {
"compute": consts.Service.NOVA,
"neutron": consts.Service.NEUTRON}
user_clients = [mock.Mock(), mock.Mock()]
self.osclients.Clients.side_effect = [admin_clients] + user_clients
user_generator._remove_default_security_group()
mock_wrap.assert_called_once_with(admin_clients)
net_wrapper.supports_security_group.assert_called_once_with()
@mock.patch("rally.common.utils.iterate_per_tenants")
@mock.patch("%s.network" % CTX)
@mock.patch("rally.task.utils.check_service_status",
return_value=False)
def test__remove_default_security_group(
self, mock_check_service_status, mock_network,
mock_iterate_per_tenants):
net_wrapper = mock.Mock(SERVICE_IMPL=consts.Service.NEUTRON)
net_wrapper.supports_security_group.return_value = (True, None)
mock_network.wrap.return_value = net_wrapper
user_generator = users.UserGenerator(self.context)
admin_clients = mock.Mock()
admin_clients.services.return_value = {
"compute": consts.Service.NOVA,
"neutron": consts.Service.NEUTRON}
user_clients = [mock.Mock(), mock.Mock()]
self.osclients.Clients.side_effect = [admin_clients] + user_clients
mock_iterate_per_tenants.return_value = [
(mock.MagicMock(), "t1"),
(mock.MagicMock(), "t2")]
user_generator._remove_default_security_group()
mock_network.wrap.assert_called_once_with(admin_clients)
mock_iterate_per_tenants.assert_called_once_with(
user_generator.context["users"])
expected = [mock.call(user_generator.endpoint)] + [
mock.call(u["endpoint"])
for u, t in mock_iterate_per_tenants.return_value]
self.osclients.Clients.assert_has_calls(expected, any_order=True)
expected_deletes = []
for clients in user_clients:
user_nova = clients.nova.return_value
user_nova.security_groups.find.assert_called_once_with(
name="default")
expected_deletes.append(
mock.call(user_nova.security_groups.find.return_value.id))
nova_admin = admin_clients.neutron.return_value
nova_admin.delete_security_group.assert_has_calls(expected_deletes,
any_order=True)
@mock.patch("rally.task.utils.check_service_status",
return_value=True)
def test__remove_associated_networks(self, mock_check_service_status):
def fake_get_network(req_network):
for network in networks:
if network.project_id == req_network.project_id:
return network
networks = [mock.MagicMock(project_id="t1"),
mock.MagicMock(project_id="t4")]
nova_admin = mock.MagicMock()
clients = mock.MagicMock()
self.osclients.Clients.return_value = clients
clients.services.return_value = {"compute": "nova"}
clients.nova.return_value = nova_admin
nova_admin.networks.list.return_value = networks
nova_admin.networks.get = fake_get_network
user_generator = users.UserGenerator(self.context)
user_generator.context["tenants"] = {"t1": dict(id="t1", name="t1"),
"t2": dict(id="t2", name="t2")}
user_generator._remove_associated_networks()
mock_check_service_status.assert_called_once_with(mock.ANY,
"nova-network")
nova_admin.networks.disassociate.assert_called_once_with(networks[0])
@mock.patch("rally.task.utils.check_service_status",
return_value=True)
def test__remove_associated_networks_failure(self,
mock_check_service_status):
def fake_get_network(req_network):
for network in networks:
if network.project_id == req_network.project_id:
return network
networks = [mock.MagicMock(project_id="t1"),
mock.MagicMock(project_id="t4")]
nova_admin = mock.MagicMock()
clients = mock.MagicMock()
self.osclients.Clients.return_value = clients
clients.services.return_value = {"compute": "nova"}
clients.nova.return_value = nova_admin
nova_admin.networks.list.return_value = networks
nova_admin.networks.get = fake_get_network
nova_admin.networks.disassociate.side_effect = Exception()
user_generator = users.UserGenerator(self.context)
user_generator.context["tenants"] = {"t1": dict(id="t1", name="t1"),
"t2": dict(id="t2", name="t2")}
user_generator._remove_associated_networks()
mock_check_service_status.assert_called_once_with(mock.ANY,
"nova-network")
nova_admin.networks.disassociate.assert_called_once_with(networks[0])
@mock.patch("%s.broker.time.sleep" % CTX)
@mock.patch("%s.keystone" % CTX)
def test__create_tenants(self, mock_keystone, mock_sleep):
user_generator = users.UserGenerator(self.context)
user_generator.config["tenants"] = 1
tenants = user_generator._create_tenants()
self.assertEqual(1, len(tenants))
id, tenant = tenants.popitem()
self.assertIn("name", tenant)
@mock.patch("%s.broker.time.sleep" % CTX)
@mock.patch("%s.keystone" % CTX)
def test__create_users(self, mock_keystone, mock_sleep):
user_generator = users.UserGenerator(self.context)
user_generator.context["tenants"] = {"t1": dict(id="t1", name="t1"),
"t2": dict(id="t2", name="t2")}
user_generator.config["users_per_tenant"] = 2
users_ = user_generator._create_users()
self.assertEqual(4, len(users_))
for user in users_:
self.assertIn("id", user)
self.assertIn("endpoint", user)
@mock.patch("%s.keystone" % CTX)
def test__delete_tenants(self, mock_keystone):
user_generator = users.UserGenerator(self.context)
user_generator.context["tenants"] = {"t1": dict(id="t1", name="t1"),
"t2": dict(id="t2", name="t2")}
user_generator._delete_tenants()
self.assertEqual(len(user_generator.context["tenants"]), 0)
@mock.patch("%s.keystone" % CTX)
def test__delete_tenants_failure(self, mock_keystone):
wrapped_keystone = mock_keystone.wrap.return_value
wrapped_keystone.delete_project.side_effect = Exception()
user_generator = users.UserGenerator(self.context)
user_generator.context["tenants"] = {"t1": dict(id="t1", name="t1"),
"t2": dict(id="t2", name="t2")}
user_generator._delete_tenants()
self.assertEqual(len(user_generator.context["tenants"]), 0)
@mock.patch("%s.keystone" % CTX)
def test__delete_users(self, mock_keystone):
user_generator = users.UserGenerator(self.context)
user1 = mock.MagicMock()
user2 = mock.MagicMock()
user_generator.context["users"] = [user1, user2]
user_generator._delete_users()
self.assertEqual(len(user_generator.context["users"]), 0)
@mock.patch("%s.keystone" % CTX)
def test__delete_users_failure(self, mock_keystone):
wrapped_keystone = mock_keystone.wrap.return_value
wrapped_keystone.delete_user.side_effect = Exception()
user_generator = users.UserGenerator(self.context)
user1 = mock.MagicMock()
user2 = mock.MagicMock()
user_generator.context["users"] = [user1, user2]
user_generator._delete_users()
self.assertEqual(len(user_generator.context["users"]), 0)
@mock.patch("%s.keystone" % CTX)
def test_setup_and_cleanup(self, mock_keystone):
wrapped_keystone = mock.MagicMock()
mock_keystone.wrap.return_value = wrapped_keystone
with users.UserGenerator(self.context) as ctx:
ctx.setup()
self.assertEqual(len(ctx.context["users"]),
self.users_num)
self.assertEqual(len(ctx.context["tenants"]),
self.tenants_num)
# Cleanup (called by content manager)
self.assertEqual(len(ctx.context["users"]), 0)
self.assertEqual(len(ctx.context["tenants"]), 0)
@mock.patch("%s.keystone" % CTX)
def test_setup_and_cleanup_failure(self, mock_keystone):
wrapped_keystone = mock_keystone.wrap.return_value
wrapped_keystone.create_user.side_effect = Exception()
with users.UserGenerator(self.context) as ctx:
self.assertRaises(exceptions.ContextSetupFailure, ctx.setup)
# Ensure that tenants get deleted anyway
self.assertEqual(len(ctx.context["tenants"]), 0)
@mock.patch("%s.keystone" % CTX)
def test_users_and_tenants_in_context(self, mock_keystone):
wrapped_keystone = mock.MagicMock()
mock_keystone.wrap.return_value = wrapped_keystone
task = {"uuid": "abcdef"}
config = {
"config": {
"users": {
"tenants": 1,
"users_per_tenant": 2,
"resource_management_workers": 1
}
},
"admin": {"endpoint": mock.MagicMock()},
"task": task
}
user_list = [mock.MagicMock(id="id_%d" % i)
for i in range(self.users_num)]
wrapped_keystone.create_user.side_effect = user_list
with users.UserGenerator(config) as ctx:
ctx.setup()
create_tenant_calls = []
for i, t in enumerate(ctx.context["tenants"]):
pattern = users.UserGenerator.PATTERN_TENANT
create_tenant_calls.append(
mock.call(pattern % {"task_id": task["uuid"], "iter": i},
ctx.config["project_domain"]))
for user in ctx.context["users"]:
self.assertEqual(set(["id", "endpoint", "tenant_id"]),
set(user.keys()))
tenants_ids = []
for t in ctx.context["tenants"].keys():
tenants_ids.append(t)
for (user, tenant_id, orig_user) in zip(ctx.context["users"],
tenants_ids, user_list):
self.assertEqual(user["id"], orig_user.id)
self.assertEqual(user["tenant_id"], tenant_id)
@mock.patch("%s.keystone" % CTX)
def test_users_contains_correct_endpoint_type(self, mock_keystone):
endpoint = objects.Endpoint("foo_url", "foo", "foo_pass",
endpoint_type=consts.EndpointType.INTERNAL)
config = {
"config": {
"users": {
"tenants": 1,
"users_per_tenant": 2,
"resource_management_workers": 1
}
},
"admin": {"endpoint": endpoint},
"task": {"uuid": "task_id"}
}
user_generator = users.UserGenerator(config)
users_ = user_generator._create_users()
for user in users_:
self.assertEqual("internal", user["endpoint"].endpoint_type)
@mock.patch("%s.keystone" % CTX)
def test_users_contains_default_endpoint_type(self, mock_keystone):
endpoint = objects.Endpoint("foo_url", "foo", "foo_pass")
config = {
"config": {
"users": {
"tenants": 1,
"users_per_tenant": 2,
"resource_management_workers": 1
}
},
"admin": {"endpoint": endpoint},
"task": {"uuid": "task_id"}
}
user_generator = users.UserGenerator(config)
users_ = user_generator._create_users()
for user in users_:
self.assertEqual("public", user["endpoint"].endpoint_type)

View File

@ -0,0 +1,93 @@
# 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.context.murano import murano_packages
from tests.unit import test
CTX = "rally.plugins.openstack.context.murano.murano_packages"
class MuranoGeneratorTestCase(test.TestCase):
def setUp(self):
super(MuranoGeneratorTestCase, self).setUp()
@staticmethod
def _get_context():
return {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 1,
"concurrent": 1,
},
"murano_packages": {
"app_package": (
"rally-jobs/extra/murano/"
"applications/HelloReporter/"
"io.murano.apps.HelloReporter.zip")
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": [
{
"id": "user_0",
"tenant_id": "tenant_0",
"endpoint": "endpoint"
},
{
"id": "user_1",
"tenant_id": "tenant_1",
"endpoint": "endpoint"
}
],
"tenants": {
"tenant_0": {"name": "tenant_0_name"},
"tenant_1": {"name": "tenant_1_name"}
}
}
@mock.patch("%s.osclients" % CTX)
def test_setup(self, mock_osclients):
mock_app = mock.MagicMock(id="fake_app_id")
(mock_osclients.Clients().murano().
packages.create.return_value) = mock_app
murano_ctx = murano_packages.PackageGenerator(self._get_context())
murano_ctx.setup()
self.assertEqual(2, len(murano_ctx.context["tenants"]))
tenant_id = murano_ctx.context["users"][0]["tenant_id"]
self.assertEqual([mock_app],
murano_ctx.context["tenants"][tenant_id]["packages"])
@mock.patch("%s.osclients" % CTX)
@mock.patch("%s.resource_manager.cleanup" % CTX)
def test_cleanup(self, mock_cleanup, mock_osclients):
mock_app = mock.Mock(id="fake_app_id")
(mock_osclients.Clients().murano().
packages.create.return_value) = mock_app
murano_ctx = murano_packages.PackageGenerator(self._get_context())
murano_ctx.setup()
murano_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["murano.packages"],
users=murano_ctx.context["users"])

View File

@ -0,0 +1,146 @@
# Copyright 2014: 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.context.network import allow_ssh
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context.network.allow_ssh"
class AllowSSHContextTestCase(test.TestCase):
def setUp(self):
super(AllowSSHContextTestCase, self).setUp()
self.users = 2
task = {"uuid": "foo_task_id"}
self.secgroup_name = allow_ssh.SSH_GROUP_NAME + "_foo"
self.ctx_with_secgroup = {
"users": [
{
"tenant_id": "uuid1",
"endpoint": "endpoint",
"secgroup": {"id": "secgroup_id", "name": "secgroup"}
}
] * self.users,
"admin": {"tenant_id": "uuid2", "endpoint": "admin_endpoint"},
"tenants": {"uuid1": {"id": "uuid1", "name": "uuid1"}},
"task": task
}
self.ctx_without_secgroup = {
"users": [{"tenant_id": "uuid1",
"endpoint": "endpoint"},
{"tenant_id": "uuid1",
"endpoint": "endpoint"}],
"admin": {"tenant_id": "uuid2", "endpoint": "admin_endpoint"},
"tenants": {"uuid1": {"id": "uuid1", "name": "uuid1"}},
"task": task
}
@mock.patch("%s.osclients.Clients" % CTX)
def test__prepare_open_secgroup(self, mock_clients):
fake_nova = fakes.FakeNovaClient()
self.assertEqual(len(fake_nova.security_groups.list()), 1)
mock_cl = mock.MagicMock()
mock_cl.nova.return_value = fake_nova
mock_clients.return_value = mock_cl
ret = allow_ssh._prepare_open_secgroup("endpoint", self.secgroup_name)
self.assertEqual(self.secgroup_name, ret["name"])
self.assertEqual(2, len(fake_nova.security_groups.list()))
self.assertIn(
self.secgroup_name,
[sg.name for sg in fake_nova.security_groups.list()])
# run prep again, check that another security group is not created
allow_ssh._prepare_open_secgroup("endpoint", self.secgroup_name)
self.assertEqual(2, len(fake_nova.security_groups.list()))
@mock.patch("%s.osclients.Clients" % CTX)
def test__prepare_open_secgroup_rules(self, mock_clients):
fake_nova = fakes.FakeNovaClient()
# NOTE(hughsaunders) Default security group is precreated
self.assertEqual(1, len(fake_nova.security_groups.list()))
mock_cl = mock.MagicMock()
mock_cl.nova.return_value = fake_nova
mock_clients.return_value = mock_cl
allow_ssh._prepare_open_secgroup("endpoint", self.secgroup_name)
self.assertEqual(2, len(fake_nova.security_groups.list()))
rally_open = fake_nova.security_groups.find(self.secgroup_name)
self.assertEqual(3, len(rally_open.rules))
# run prep again, check that extra rules are not created
allow_ssh._prepare_open_secgroup("endpoint", self.secgroup_name)
rally_open = fake_nova.security_groups.find(self.secgroup_name)
self.assertEqual(3, len(rally_open.rules))
@mock.patch("%s.osclients.Clients" % CTX)
@mock.patch("%s._prepare_open_secgroup" % CTX)
@mock.patch("rally.plugins.openstack.wrappers.network.wrap")
def test_secgroup_setup_cleanup_with_secgroup_supported(
self, mock_network_wrap, mock__prepare_open_secgroup,
mock_clients):
mock_network_wrapper = mock.MagicMock()
mock_network_wrapper.supports_security_group.return_value = (
True, "")
mock_network_wrap.return_value = mock_network_wrapper
mock__prepare_open_secgroup.return_value = {
"name": "secgroup",
"id": "secgroup_id"}
mock_clients.return_value = mock.MagicMock()
secgrp_ctx = allow_ssh.AllowSSH(self.ctx_without_secgroup)
secgrp_ctx.setup()
self.assertEqual(self.ctx_with_secgroup, secgrp_ctx.context)
secgrp_ctx.cleanup()
self.assertEqual(
[
mock.call("admin_endpoint"),
mock.call("endpoint"),
mock.call().nova(),
mock.call().nova().security_groups.get("secgroup_id"),
mock.call().nova().security_groups.get().delete()
],
mock_clients.mock_calls)
mock_network_wrap.assert_called_once_with(
mock_clients.return_value, {})
@mock.patch("%s.osclients.Clients" % CTX)
@mock.patch("rally.plugins.openstack.wrappers.network.wrap")
def test_secgroup_setup_with_secgroup_unsupported(
self, mock_network_wrap, mock_clients):
mock_network_wrapper = mock.MagicMock()
mock_network_wrapper.supports_security_group.return_value = (
False, "Not supported")
mock_network_wrap.return_value = mock_network_wrapper
mock_clients.return_value = mock.MagicMock()
secgrp_ctx = allow_ssh.AllowSSH(dict(self.ctx_without_secgroup))
secgrp_ctx.setup()
self.assertEqual(self.ctx_without_secgroup, secgrp_ctx.context)
mock_clients.assert_called_once_with("admin_endpoint")
mock_network_wrap.assert_called_once_with(
mock_clients.return_value, {})

View File

@ -0,0 +1,79 @@
# Copyright 2014: 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
import netaddr
from rally.plugins.openstack.context.network import networks as network_context
from tests.unit import test
NET = "rally.plugins.openstack.wrappers.network."
class NetworkTestCase(test.TestCase):
def get_context(self, **kwargs):
return {"task": {"uuid": "foo_task"},
"admin": {"endpoint": "foo_admin"},
"config": {"network": kwargs},
"tenants": {"foo_tenant": {"networks": [{"id": "foo_net"}]},
"bar_tenant": {"networks": [{"id": "bar_net"}]}}}
def test_START_CIDR_DFLT(self):
netaddr.IPNetwork(network_context.Network.DEFAULT_CONFIG["start_cidr"])
@mock.patch("rally.osclients.Clients")
@mock.patch(NET + "wrap", return_value="foo_service")
def test__init__(self, mock_wrap, mock_clients):
context = network_context.Network(self.get_context())
self.assertEqual(context.net_wrapper, "foo_service")
self.assertEqual(context.config["networks_per_tenant"], 1)
self.assertEqual(context.config["start_cidr"],
network_context.Network.DEFAULT_CONFIG["start_cidr"])
context = network_context.Network(
self.get_context(start_cidr="foo_cidr", networks_per_tenant=42))
self.assertEqual(context.net_wrapper, "foo_service")
self.assertEqual(context.config["networks_per_tenant"], 42)
self.assertEqual(context.config["start_cidr"], "foo_cidr")
@mock.patch(NET + "wrap")
@mock.patch("rally.plugins.openstack.context.network.networks.utils")
@mock.patch("rally.osclients.Clients")
def test_setup(self, mock_clients, mock_utils, mock_wrap):
mock_utils.iterate_per_tenants.return_value = [
("foo_user", "foo_tenant"),
("bar_user", "bar_tenant")]
mock_create = mock.Mock(side_effect=lambda t, **kw: t + "-net")
mock_wrap.return_value = mock.Mock(create_network=mock_create)
nets_per_tenant = 2
net_context = network_context.Network(
self.get_context(networks_per_tenant=nets_per_tenant))
net_context.setup()
expected_networks = [["bar_tenant-net"] * nets_per_tenant,
["foo_tenant-net"] * nets_per_tenant]
actual_networks = []
for tenant_id, tenant_ctx in (
sorted(net_context.context["tenants"].items())):
actual_networks.append(tenant_ctx["networks"])
self.assertEqual(expected_networks, actual_networks)
@mock.patch("rally.osclients.Clients")
@mock.patch(NET + "wrap")
def test_cleanup(self, mock_wrap, mock_clients):
net_context = network_context.Network(self.get_context())
net_context.cleanup()
mock_wrap().delete_network.assert_has_calls(
[mock.call({"id": "foo_net"}), mock.call({"id": "bar_net"})],
any_order=True)

View File

@ -0,0 +1,121 @@
# Copyright 2014: 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 copy
import mock
from novaclient import exceptions as nova_exceptions
from rally.plugins.openstack.context.nova import flavors
from tests.unit import test
CTX = "rally.plugins.openstack.context.nova"
class FlavorsGeneratorTestCase(test.TestCase):
def setUp(self):
super(FlavorsGeneratorTestCase, self).setUp()
self.context = {
"config": {
"flavors": [{
"name": "flavor_name",
"ram": 2048,
"disk": 10,
"vcpus": 3,
"ephemeral": 3,
"swap": 5,
"extra_specs": {
"key": "value"
}
}]
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
}
@mock.patch("%s.flavors.osclients.Clients" % CTX)
def test_setup(self, mock_clients):
# Setup and mock
mock_create = mock_clients().nova().flavors.create
mock_create().to_dict.return_value = {"flavor_key": "flavor_value"}
# Run
flavors_ctx = flavors.FlavorsGenerator(self.context)
flavors_ctx.setup()
# Assertions
self.assertEqual(flavors_ctx.context["flavors"],
{"flavor_name": {"flavor_key": "flavor_value"}})
mock_clients.assert_called_with(self.context["admin"]["endpoint"])
mock_create.assert_called_with(
name="flavor_name", ram=2048, vcpus=3,
disk=10, ephemeral=3, swap=5)
mock_create().set_keys.assert_called_with({"key": "value"})
mock_create().to_dict.assert_called_with()
@mock.patch("%s.flavors.osclients.Clients" % CTX)
def test_setup_failexists(self, mock_clients):
# Setup and mock
new_context = copy.deepcopy(self.context)
new_context["flavors"] = {}
mock_flavor_create = mock_clients().nova().flavors.create
exception = nova_exceptions.Conflict("conflict")
mock_flavor_create.side_effect = exception
# Run
flavors_ctx = flavors.FlavorsGenerator(self.context)
flavors_ctx.setup()
# Assertions
self.assertEqual(new_context, flavors_ctx.context)
mock_clients.assert_called_with(self.context["admin"]["endpoint"])
mock_flavor_create.assert_called_once_with(
name="flavor_name", ram=2048, vcpus=3,
disk=10, ephemeral=3, swap=5)
@mock.patch("%s.flavors.osclients.Clients" % CTX)
def test_cleanup(self, mock_clients):
# Setup and mock
real_context = {
"flavors": {
"flavor_name": {
"flavor_name": "flavor_name",
"id": "flavor_name"
}
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
}
# Run
flavors_ctx = flavors.FlavorsGenerator(real_context)
flavors_ctx.cleanup()
# Assertions
mock_clients.assert_called_with(real_context["admin"]["endpoint"])
mock_flavors_delete = mock_clients().nova().flavors.delete
mock_flavors_delete.assert_called_with("flavor_name")

View File

@ -0,0 +1,92 @@
# Copyright 2014: Rackspace UK
# 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.context.nova import keypairs
from tests.unit import test
CTX = "rally.plugins.openstack.context.nova"
class KeyPairContextTestCase(test.TestCase):
def setUp(self):
super(KeyPairContextTestCase, self).setUp()
self.users = 2
self.keypair_name = keypairs.Keypair.KEYPAIR_NAME + "_foo_task_id"
task = {"uuid": "foo_task_id"}
self.ctx_with_keys = {
"users": [
{
"keypair": {
"id": "key_id",
"key": "key",
"name": self.keypair_name
},
"endpoint": "endpoint"
},
] * self.users,
"task": task
}
self.ctx_without_keys = {
"users": [{"endpoint": "endpoint"}] * self.users,
"task": task
}
@mock.patch("%s.keypairs.Keypair._generate_keypair" % CTX)
def test_keypair_setup(self, mock_keypair__generate_keypair):
mock_keypair__generate_keypair.side_effect = [
{"id": "key_id", "key": "key", "name": self.keypair_name},
{"id": "key_id", "key": "key", "name": self.keypair_name},
]
keypair_ctx = keypairs.Keypair(self.ctx_without_keys)
keypair_ctx.setup()
self.assertEqual(self.ctx_with_keys, keypair_ctx.context)
self.assertEqual(
[mock.call("endpoint")] * 2,
mock_keypair__generate_keypair.mock_calls)
@mock.patch("%s.keypairs.resource_manager.cleanup" % CTX)
def test_keypair_cleanup(self, mock_cleanup):
keypair_ctx = keypairs.Keypair(self.ctx_with_keys)
keypair_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["nova.keypairs"],
users=self.ctx_with_keys["users"])
@mock.patch("rally.osclients.Clients")
def test_keypair_generate(self, mock_clients):
mock_keypairs = mock_clients.return_value.nova.return_value.keypairs
mock_keypair = mock_keypairs.create.return_value
mock_keypair.public_key = "public_key"
mock_keypair.private_key = "private_key"
mock_keypair.id = "key_id"
keypair_ctx = keypairs.Keypair(self.ctx_without_keys)
key = keypair_ctx._generate_keypair("endpoint")
self.assertEqual({
"id": "key_id",
"name": "rally_ssh_key_foo_task_id",
"private": "private_key",
"public": "public_key"
}, key)
mock_clients.assert_has_calls([
mock.call().nova().keypairs.delete("rally_ssh_key_foo_task_id"),
mock.call().nova().keypairs.create("rally_ssh_key_foo_task_id"),
])

View File

@ -0,0 +1,162 @@
# 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 copy
import mock
from rally.plugins.openstack.context.nova import servers
from tests.unit import fakes
from tests.unit import test
CTX = "rally.plugins.openstack.context.nova"
SCN = "rally.plugins.openstack.scenarios"
TYP = "rally.task.types"
class ServerGeneratorTestCase(test.ScenarioTestCase):
def _gen_tenants(self, count):
tenants = {}
for id_ in range(count):
tenants[str(id_)] = {"name": str(id_)}
return tenants
def test_init(self):
tenants_count = 2
servers_per_tenant = 5
context = {}
context["task"] = mock.MagicMock()
context["config"] = {
"servers": {
"servers_per_tenant": servers_per_tenant,
}
}
context["tenants"] = self._gen_tenants(tenants_count)
inst = servers.ServerGenerator(context)
self.assertEqual(context["config"]["servers"], inst.config)
@mock.patch("%s.nova.utils.NovaScenario._boot_servers" % SCN,
return_value=[
fakes.FakeServer(id="uuid"),
fakes.FakeServer(id="uuid"),
fakes.FakeServer(id="uuid"),
fakes.FakeServer(id="uuid"),
fakes.FakeServer(id="uuid")
])
@mock.patch("%s.ImageResourceType.transform" % TYP,
return_value=mock.MagicMock())
@mock.patch("%s.FlavorResourceType.transform" % TYP,
return_value=mock.MagicMock())
@mock.patch("%s.servers.osclients" % CTX, return_value=fakes.FakeClients())
def test_setup(self, mock_osclients, mock_flavor_resource_type_transform,
mock_image_resource_type_transform,
mock_nova_scenario__boot_servers):
tenants_count = 2
users_per_tenant = 5
servers_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants.keys():
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": mock.MagicMock()})
real_context = {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 5,
"concurrent": 10,
},
"servers": {
"servers_per_tenant": 5,
"image": {
"name": "cirros-0.3.2-x86_64-uec",
},
"flavor": {
"name": "m1.tiny",
},
},
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
new_context = copy.deepcopy(real_context)
for id_ in new_context["tenants"]:
new_context["tenants"][id_].setdefault("servers", [])
for i in range(servers_per_tenant):
new_context["tenants"][id_]["servers"].append("uuid")
servers_ctx = servers.ServerGenerator(real_context)
servers_ctx.setup()
self.assertEqual(new_context, real_context)
@mock.patch("%s.servers.osclients" % CTX)
@mock.patch("%s.servers.resource_manager.cleanup" % CTX)
def test_cleanup(self, mock_cleanup, mock_osclients):
tenants_count = 2
users_per_tenant = 5
servers_per_tenant = 5
tenants = self._gen_tenants(tenants_count)
users = []
for id_ in tenants.keys():
for i in range(users_per_tenant):
users.append({"id": i, "tenant_id": id_,
"endpoint": "endpoint"})
tenants[id_].setdefault("servers", [])
for j in range(servers_per_tenant):
tenants[id_]["servers"].append("uuid")
context = {
"config": {
"users": {
"tenants": 2,
"users_per_tenant": 5,
"concurrent": 10,
},
"servers": {
"servers_per_tenant": 5,
"image": {
"name": "cirros-0.3.2-x86_64-uec",
},
"flavor": {
"name": "m1.tiny",
},
},
},
"admin": {
"endpoint": mock.MagicMock()
},
"task": mock.MagicMock(),
"users": users,
"tenants": tenants
}
servers_ctx = servers.ServerGenerator(context)
servers_ctx.cleanup()
mock_cleanup.assert_called_once_with(names=["nova.servers"],
users=context["users"])