Add boot server scenario for EC2 API
Add ec2 client to osclients and add boot server scenario Change-Id: I78b3feced22071f62b1a2bd03c3b093402954c50
This commit is contained in:
parent
13de04f09c
commit
e6199e2f07
@ -365,6 +365,16 @@
|
||||
# Cluster status polling interval in seconds (integer value)
|
||||
#job_check_interval = 5
|
||||
|
||||
# Time to sleep after boot before polling for status (floating point
|
||||
# value)
|
||||
#ec2_server_boot_prepoll_delay = 1.0
|
||||
|
||||
# Server boot timeout (floating point value)
|
||||
#ec2_server_boot_timeout = 300.0
|
||||
|
||||
# Server boot poll interval (floating point value)
|
||||
#ec2_server_boot_poll_interval = 1.0
|
||||
|
||||
|
||||
[database]
|
||||
|
||||
@ -455,19 +465,20 @@
|
||||
# lost. (boolean value)
|
||||
#use_db_reconnect = false
|
||||
|
||||
# Seconds between database connection retries. (integer value)
|
||||
# Seconds between retries of a database transaction. (integer value)
|
||||
#db_retry_interval = 1
|
||||
|
||||
# If True, increases the interval between database connection retries
|
||||
# up to db_max_retry_interval. (boolean value)
|
||||
# If True, increases the interval between retries of a database
|
||||
# operation up to db_max_retry_interval. (boolean value)
|
||||
#db_inc_retry_interval = true
|
||||
|
||||
# If db_inc_retry_interval is set, the maximum seconds between
|
||||
# database connection retries. (integer value)
|
||||
# If db_inc_retry_interval is set, the maximum seconds between retries
|
||||
# of a database operation. (integer value)
|
||||
#db_max_retry_interval = 10
|
||||
|
||||
# Maximum database connection retries before error is raised. Set to
|
||||
# -1 to specify an infinite retry count. (integer value)
|
||||
# Maximum retries in case of connection error or deadlock error before
|
||||
# error is raised. Set to -1 to specify an infinite retry count.
|
||||
# (integer value)
|
||||
#db_max_retries = 20
|
||||
|
||||
|
||||
|
@ -1448,3 +1448,22 @@
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
|
||||
EC2Servers.boot_server:
|
||||
-
|
||||
args:
|
||||
flavor:
|
||||
name: "m1.tiny"
|
||||
image:
|
||||
name: {{image_name}}
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 3
|
||||
concurrency: 3
|
||||
context:
|
||||
users:
|
||||
tenants: 3
|
||||
users_per_tenant: 1
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from boto import exception as boto_exception
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
from saharaclient.api import base as saharaclient_base
|
||||
|
||||
@ -83,6 +84,44 @@ class NovaQuotas(QuotaMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
# EC2
|
||||
|
||||
_ec2_order = get_order(250)
|
||||
|
||||
|
||||
class EC2Mixin(object):
|
||||
|
||||
def _manager(self):
|
||||
return getattr(self.user, self._service)()
|
||||
|
||||
|
||||
@base.resource("ec2", "servers", order=next(_ec2_order))
|
||||
class EC2Server(EC2Mixin, base.ResourceManager):
|
||||
|
||||
def is_deleted(self):
|
||||
try:
|
||||
instances = self._manager().get_only_instances(
|
||||
instance_ids=[self.id()])
|
||||
except boto_exception.EC2ResponseError as e:
|
||||
# NOTE(wtakase): Nova EC2 API returns 'InvalidInstanceID.NotFound'
|
||||
# if instance not found. In this case, we consider
|
||||
# instance has already been deleted.
|
||||
return getattr(e, "error_code") == "InvalidInstanceID.NotFound"
|
||||
|
||||
# NOTE(wtakase): After instance deletion, instance can be 'terminated'
|
||||
# state. If all instance states are 'terminated', this
|
||||
# returns True. And if get_only_instaces() returns empty
|
||||
# list, this also returns True because we consider
|
||||
# instance has already been deleted.
|
||||
return all(map(lambda i: i.state == "terminated", instances))
|
||||
|
||||
def delete(self):
|
||||
self._manager().terminate_instances(instance_ids=[self.id()])
|
||||
|
||||
def list(self):
|
||||
return self._manager().get_only_instances()
|
||||
|
||||
|
||||
# NEUTRON
|
||||
|
||||
_neutron_order = get_order(300)
|
||||
|
0
rally/benchmark/scenarios/ec2/__init__.py
Normal file
0
rally/benchmark/scenarios/ec2/__init__.py
Normal file
44
rally/benchmark/scenarios/ec2/servers.py
Normal file
44
rally/benchmark/scenarios/ec2/servers.py
Normal file
@ -0,0 +1,44 @@
|
||||
# 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.benchmark.scenarios import base
|
||||
from rally.benchmark.scenarios.ec2 import utils
|
||||
from rally.benchmark import types
|
||||
from rally.benchmark import validation
|
||||
from rally.common import log as logging
|
||||
from rally import consts
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EC2Servers(utils.EC2Scenario):
|
||||
"""Benchmark scenarios for servers using EC2."""
|
||||
|
||||
@types.set(image=types.EC2ImageResourceType,
|
||||
flavor=types.EC2FlavorResourceType)
|
||||
@validation.image_valid_on_flavor("flavor", "image")
|
||||
@validation.required_services(consts.Service.EC2)
|
||||
@validation.required_openstack(users=True)
|
||||
@base.scenario(context={"cleanup": ["ec2"]})
|
||||
def boot_server(self, image, flavor, **kwargs):
|
||||
"""Boot a server.
|
||||
|
||||
Assumes that cleanup is done elsewhere.
|
||||
|
||||
:param image: image to be used to boot an instance
|
||||
:param flavor: flavor to be used to boot an instance
|
||||
:param kwargs: optional additional arguments for server creation
|
||||
"""
|
||||
self._boot_server(image, flavor, **kwargs)
|
86
rally/benchmark/scenarios/ec2/utils.py
Normal file
86
rally/benchmark/scenarios/ec2/utils.py
Normal file
@ -0,0 +1,86 @@
|
||||
# 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 time
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from rally.benchmark.scenarios import base
|
||||
from rally.benchmark import utils as bench_utils
|
||||
|
||||
|
||||
EC2_BENCHMARK_OPTS = [
|
||||
cfg.FloatOpt(
|
||||
"ec2_server_boot_prepoll_delay",
|
||||
default=1.0,
|
||||
help="Time to sleep after boot before polling for status"
|
||||
),
|
||||
cfg.FloatOpt(
|
||||
"ec2_server_boot_timeout",
|
||||
default=300.0,
|
||||
help="Server boot timeout"
|
||||
),
|
||||
cfg.FloatOpt(
|
||||
"ec2_server_boot_poll_interval",
|
||||
default=1.0,
|
||||
help="Server boot poll interval"
|
||||
)
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
benchmark_group = cfg.OptGroup(name="benchmark",
|
||||
title="benchmark options")
|
||||
CONF.register_opts(EC2_BENCHMARK_OPTS, group=benchmark_group)
|
||||
|
||||
|
||||
def ec2_resource_is(status):
|
||||
"""Check status for EC2."""
|
||||
|
||||
return lambda resource: resource.state.upper() == status.upper()
|
||||
|
||||
|
||||
class EC2Scenario(base.Scenario):
|
||||
"""Base class for EC2 scenarios with basic atomic actions."""
|
||||
|
||||
RESOURCE_NAME_PREFIX = "rally_ec2server_"
|
||||
RESOURCE_NAME_LENGTH = 16
|
||||
|
||||
@base.atomic_action_timer("ec2.boot_server")
|
||||
def _boot_server(self, image_id, flavor_name, **kwargs):
|
||||
"""Boot a server.
|
||||
|
||||
Returns when the server is actually booted and in "Running" state.
|
||||
|
||||
:param image_id: ID of the image to be used for server creation
|
||||
:param flavor_name: Name of the flavor to be used for server creation
|
||||
:param kwargs: other optional parameters to initialize the server
|
||||
:returns: EC2 Server instance
|
||||
"""
|
||||
reservation = self.clients("ec2").run_instances(
|
||||
image_id=image_id, instance_type=flavor_name, **kwargs)
|
||||
server = reservation.instances[0]
|
||||
|
||||
time.sleep(CONF.benchmark.ec2_server_boot_prepoll_delay)
|
||||
server = bench_utils.wait_for(
|
||||
server,
|
||||
is_ready=ec2_resource_is("RUNNING"),
|
||||
update_resource=self._update_resource,
|
||||
timeout=CONF.benchmark.ec2_server_boot_timeout,
|
||||
check_interval=CONF.benchmark.ec2_server_boot_poll_interval
|
||||
)
|
||||
return server
|
||||
|
||||
def _update_resource(self, resource):
|
||||
resource.update()
|
||||
return resource
|
@ -133,6 +133,37 @@ def obj_from_name(resource_config, resources, typename):
|
||||
return matching[0]
|
||||
|
||||
|
||||
def obj_from_id(resource_config, resources, typename):
|
||||
"""Return the resource whose name matches the id.
|
||||
|
||||
resource_config has to contain `id`, as it is used to lookup a resource.
|
||||
|
||||
:param resource_config: resource to be transformed
|
||||
:param resources: iterable containing all resources
|
||||
:param typename: name which describes the type of resource
|
||||
|
||||
:returns: resource object mapped to `id`
|
||||
"""
|
||||
if "id" in resource_config:
|
||||
matching = [resource for resource in resources
|
||||
if resource.id == resource_config["id"]]
|
||||
if len(matching) == 1:
|
||||
return matching[0]
|
||||
elif len(matching) > 1:
|
||||
raise exceptions.MultipleMatchesFound(
|
||||
needle="{typename} with id '{id}'".format(
|
||||
typename=typename.title(), id=resource_config["id"]),
|
||||
haystack=matching)
|
||||
else:
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"{typename} with id '{id}' not found".format(
|
||||
typename=typename.title(), id=resource_config["id"]))
|
||||
else:
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"{typename} 'id' not found in '{resource_config}'".format(
|
||||
typename=typename.title(), resource_config=resource_config))
|
||||
|
||||
|
||||
def _id_from_name(resource_config, resources, typename):
|
||||
"""Return the id of the resource whose name matches the pattern.
|
||||
|
||||
@ -151,6 +182,20 @@ def _id_from_name(resource_config, resources, typename):
|
||||
return obj_from_name(resource_config, resources, typename).id
|
||||
|
||||
|
||||
def _name_from_id(resource_config, resources, typename):
|
||||
"""Return the name of the resource which has the id.
|
||||
|
||||
resource_config has to contain `id`, as it is used to lookup an name.
|
||||
|
||||
:param resource_config: resource to be transformed
|
||||
:param resources: iterable containing all resources
|
||||
:param typename: name which describes the type of resource
|
||||
|
||||
:returns: resource name mapped to `id`
|
||||
"""
|
||||
return obj_from_id(resource_config, resources, typename).name
|
||||
|
||||
|
||||
class FlavorResourceType(ResourceType):
|
||||
|
||||
@classmethod
|
||||
@ -171,6 +216,30 @@ class FlavorResourceType(ResourceType):
|
||||
return resource_id
|
||||
|
||||
|
||||
class EC2FlavorResourceType(ResourceType):
|
||||
|
||||
@classmethod
|
||||
def transform(cls, clients, resource_config):
|
||||
"""Transform the resource config to name.
|
||||
|
||||
In the case of using EC2 API, flavor name is used for launching
|
||||
servers.
|
||||
|
||||
:param clients: openstack admin client handles
|
||||
:param resource_config: scenario config with `id`, `name` or `regex`
|
||||
|
||||
:returns: name matching resource
|
||||
"""
|
||||
resource_name = resource_config.get("name")
|
||||
if not resource_name:
|
||||
# NOTE(wtakase): gets resource name from OpenStack id
|
||||
novaclient = clients.nova()
|
||||
resource_name = _name_from_id(resource_config=resource_config,
|
||||
resources=novaclient.flavors.list(),
|
||||
typename="flavor")
|
||||
return resource_name
|
||||
|
||||
|
||||
class ImageResourceType(ResourceType):
|
||||
|
||||
@classmethod
|
||||
@ -192,6 +261,38 @@ class ImageResourceType(ResourceType):
|
||||
return resource_id
|
||||
|
||||
|
||||
class EC2ImageResourceType(ResourceType):
|
||||
|
||||
@classmethod
|
||||
def transform(cls, clients, resource_config):
|
||||
"""Transform the resource config to EC2 id.
|
||||
|
||||
If OpenStack resource id is given, this function gets resource name
|
||||
from the id and then gets EC2 resource id from the name.
|
||||
|
||||
:param clients: openstack admin client handles
|
||||
:param resource_config: scenario config with `id`, `name` or `regex`
|
||||
|
||||
:returns: EC2 id matching resource
|
||||
"""
|
||||
if "name" not in resource_config and "regex" not in resource_config:
|
||||
# NOTE(wtakase): gets resource name from OpenStack id
|
||||
glanceclient = clients.glance()
|
||||
resource_name = _name_from_id(resource_config=resource_config,
|
||||
resources=list(
|
||||
glanceclient.images.list()),
|
||||
typename="image")
|
||||
resource_config["name"] = resource_name
|
||||
|
||||
# NOTE(wtakase): gets EC2 resource id from name or regex
|
||||
ec2client = clients.ec2()
|
||||
resource_ec2_id = _id_from_name(resource_config=resource_config,
|
||||
resources=list(
|
||||
ec2client.get_all_images()),
|
||||
typename="ec2_image")
|
||||
return resource_ec2_id
|
||||
|
||||
|
||||
class VolumeTypeResourceType(ResourceType):
|
||||
|
||||
@classmethod
|
||||
|
@ -368,6 +368,11 @@ def run(argv, categories):
|
||||
urllib3_log = logging.getLogger("urllib3").logger
|
||||
urllib3_log.setLevel(logging.WARNING)
|
||||
|
||||
# NOTE(wtakase): This is for suppressing boto error logging.
|
||||
LOG.debug("ERROR log from boto module is hide.")
|
||||
boto_log = logging.getLogger("boto").logger
|
||||
boto_log.setLevel(logging.CRITICAL)
|
||||
|
||||
except cfg.ConfigFilesNotFoundError:
|
||||
cfgfile = CONF.config_file[-1] if CONF.config_file else None
|
||||
if cfgfile and not os.access(cfgfile, os.R_OK):
|
||||
|
@ -16,6 +16,7 @@ import itertools
|
||||
|
||||
from rally.benchmark.context import users
|
||||
from rally.benchmark.scenarios.cinder import utils as cinder_utils
|
||||
from rally.benchmark.scenarios.ec2 import utils as ec2_utils
|
||||
from rally.benchmark.scenarios.glance import utils as glance_utils
|
||||
from rally.benchmark.scenarios.heat import utils as heat_utils
|
||||
from rally.benchmark.scenarios.nova import utils as nova_utils
|
||||
@ -37,7 +38,8 @@ def list_opts():
|
||||
glance_utils.GLANCE_BENCHMARK_OPTS,
|
||||
heat_utils.HEAT_BENCHMARK_OPTS,
|
||||
nova_utils.NOVA_BENCHMARK_OPTS,
|
||||
sahara_utils.SAHARA_TIMEOUT_OPTS)),
|
||||
sahara_utils.SAHARA_TIMEOUT_OPTS,
|
||||
ec2_utils.EC2_BENCHMARK_OPTS)),
|
||||
("image",
|
||||
itertools.chain(tempest_conf.IMAGE_OPTS)),
|
||||
("users_context", itertools.chain(users.USER_CONTEXT_OPTS))
|
||||
|
@ -33,6 +33,7 @@ from swiftclient import client as swift
|
||||
from troveclient import client as trove
|
||||
from zaqarclient.queues import client as zaqar
|
||||
|
||||
from rally.common.i18n import _
|
||||
from rally.common import log as logging
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
@ -366,6 +367,28 @@ class Clients(object):
|
||||
cacert=CONF.https_cacert)
|
||||
return client
|
||||
|
||||
@cached
|
||||
def ec2(self):
|
||||
"""Return ec2 client."""
|
||||
import boto
|
||||
kc = self.keystone()
|
||||
if kc.version != "v2.0":
|
||||
raise exceptions.RallyException(
|
||||
_("Rally EC2 benchmark currently supports only"
|
||||
"Keystone version 2"))
|
||||
ec2_credential = kc.ec2.create(user_id=kc.auth_user_id,
|
||||
tenant_id=kc.auth_tenant_id)
|
||||
ec2_api_url = kc.service_catalog.url_for(
|
||||
service_type=consts.ServiceType.EC2,
|
||||
endpoint_type=self.endpoint.endpoint_type,
|
||||
region_name=self.endpoint.region_name)
|
||||
client = boto.connect_ec2_endpoint(
|
||||
url=ec2_api_url,
|
||||
aws_access_key_id=ec2_credential.access,
|
||||
aws_secret_access_key=ec2_credential.secret,
|
||||
is_secure=CONF.https_insecure)
|
||||
return client
|
||||
|
||||
@cached
|
||||
def services(self):
|
||||
"""Return available services names and types.
|
||||
|
@ -2,6 +2,7 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
Babel>=1.3
|
||||
boto>=2.32.1
|
||||
decorator>=3.4.0
|
||||
fixtures>=0.3.14
|
||||
iso8601>=0.1.9
|
||||
|
25
samples/tasks/scenarios/ec2/boot.json
Normal file
25
samples/tasks/scenarios/ec2/boot.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"EC2Servers.boot_server": [
|
||||
{
|
||||
"args": {
|
||||
"flavor": {
|
||||
"name": "m1.nano"
|
||||
},
|
||||
"image": {
|
||||
"name": "^cirros.*uec$"
|
||||
}
|
||||
},
|
||||
"runner": {
|
||||
"type": "constant",
|
||||
"times": 10,
|
||||
"concurrency": 2
|
||||
},
|
||||
"context": {
|
||||
"users": {
|
||||
"tenants": 3,
|
||||
"users_per_tenant": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
16
samples/tasks/scenarios/ec2/boot.yaml
Normal file
16
samples/tasks/scenarios/ec2/boot.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
EC2Servers.boot_server:
|
||||
-
|
||||
args:
|
||||
flavor:
|
||||
name: "m1.nano"
|
||||
image:
|
||||
name: "^cirros.*uec$"
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 10
|
||||
concurrency: 2
|
||||
context:
|
||||
users:
|
||||
tenants: 3
|
||||
users_per_tenant: 2
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from boto import exception as boto_exception
|
||||
import mock
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
|
||||
@ -95,6 +96,72 @@ class NovaSecurityGroupTestCase(test.TestCase):
|
||||
resources.NovaSecurityGroup().list())
|
||||
|
||||
|
||||
class EC2MixinTestCase(test.TestCase):
|
||||
|
||||
def get_ec2_mixin(self):
|
||||
ec2 = resources.EC2Mixin()
|
||||
ec2._service = "ec2"
|
||||
return ec2
|
||||
|
||||
def test__manager(self):
|
||||
ec2 = self.get_ec2_mixin()
|
||||
ec2.user = mock.MagicMock()
|
||||
self.assertEqual(ec2.user.ec2.return_value, ec2._manager())
|
||||
|
||||
|
||||
class EC2ServerTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_is_deleted(self, mock_manager):
|
||||
raw_res1 = mock.MagicMock(state="terminated")
|
||||
raw_res2 = mock.MagicMock(state="terminated")
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
manager = resources.EC2Server(resource=resource)
|
||||
|
||||
mock_manager().get_only_instances.return_value = [raw_res1]
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
raw_res1.state = "running"
|
||||
self.assertFalse(manager.is_deleted())
|
||||
|
||||
mock_manager().get_only_instances.return_value = [raw_res1, raw_res2]
|
||||
self.assertFalse(manager.is_deleted())
|
||||
|
||||
raw_res1.state = "terminated"
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
mock_manager().get_only_instances.return_value = []
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_is_deleted_exceptions(self, mock_manager):
|
||||
mock_manager.side_effect = [
|
||||
boto_exception.EC2ResponseError(
|
||||
status="fake", reason="fake",
|
||||
body={"Error": {"Code": "fake_code"}}),
|
||||
boto_exception.EC2ResponseError(
|
||||
status="fake", reason="fake",
|
||||
body={"Error": {"Code": "InvalidInstanceID.NotFound"}})
|
||||
]
|
||||
manager = resources.EC2Server(resource=mock.MagicMock())
|
||||
self.assertFalse(manager.is_deleted())
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_delete(self, mock_manager):
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
manager = resources.EC2Server(resource=resource)
|
||||
manager.delete()
|
||||
mock_manager().terminate_instances.assert_called_once_with(
|
||||
instance_ids=["test_id"])
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_list(self, mock_manager):
|
||||
manager = resources.EC2Server()
|
||||
mock_manager().get_only_instances.return_value = ["a", "b", "c"]
|
||||
self.assertEqual(["a", "b", "c"], manager.list())
|
||||
|
||||
|
||||
class NeutronMixinTestCase(test.TestCase):
|
||||
|
||||
def get_neutron_mixin(self):
|
||||
|
0
tests/unit/benchmark/scenarios/ec2/__init__.py
Normal file
0
tests/unit/benchmark/scenarios/ec2/__init__.py
Normal file
48
tests/unit/benchmark/scenarios/ec2/test_servers.py
Normal file
48
tests/unit/benchmark/scenarios/ec2/test_servers.py
Normal file
@ -0,0 +1,48 @@
|
||||
# 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.benchmark.scenarios.ec2 import servers
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
UTILS = "rally.benchmark.scenarios.ec2.utils."
|
||||
|
||||
|
||||
class EC2ServersTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("rally.benchmark.utils.wait_for",
|
||||
return_value="running_server")
|
||||
@mock.patch(UTILS + "ec2_resource_is", return_value="foo_state")
|
||||
@mock.patch(UTILS + "time")
|
||||
@mock.patch(UTILS + "CONF")
|
||||
def test_boot_server(self, mock_conf, mock_time, mock_is, mock_wait):
|
||||
mock_conf.benchmark.ec2_server_boot_prepoll_delay = "foo_delay"
|
||||
mock_conf.benchmark.ec2_server_boot_timeout = "foo_timeout"
|
||||
mock_conf.benchmark.ec2_server_boot_poll_interval = "foo_interval"
|
||||
|
||||
scenario = servers.EC2Servers()
|
||||
scenario._update_resource = "foo_update"
|
||||
mock_instances = mock.Mock(instances=["foo_inst"])
|
||||
scenario.clients = mock.Mock()
|
||||
scenario.clients("ec2").run_instances.return_value = mock_instances
|
||||
server = scenario._boot_server("foo_image", "foo_flavor", foo="bar")
|
||||
|
||||
mock_wait.assert_called_once_with("foo_inst", is_ready="foo_state",
|
||||
update_resource="foo_update",
|
||||
timeout="foo_timeout",
|
||||
check_interval="foo_interval")
|
||||
mock_time.sleep.assert_called_once_with("foo_delay")
|
||||
self.assertEqual(server, "running_server")
|
75
tests/unit/benchmark/scenarios/ec2/test_utils.py
Normal file
75
tests/unit/benchmark/scenarios/ec2/test_utils.py
Normal file
@ -0,0 +1,75 @@
|
||||
# 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 oslo_config import cfg
|
||||
from oslotest import mockpatch
|
||||
|
||||
from rally.benchmark.scenarios.ec2 import utils
|
||||
from tests.unit import test
|
||||
|
||||
EC2_UTILS = "rally.benchmark.scenarios.ec2.utils"
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class EC2UtilsTestCase(test.TestCase):
|
||||
|
||||
def test_ec2_resource_is(self):
|
||||
resource = mock.MagicMock(state="RUNNING")
|
||||
resource_is = utils.ec2_resource_is("RUNNING")
|
||||
self.assertTrue(resource_is(resource))
|
||||
resource.state = "PENDING"
|
||||
self.assertFalse(resource_is(resource))
|
||||
|
||||
def test__update_resource(self):
|
||||
resource = mock.MagicMock()
|
||||
utils.EC2Scenario()._update_resource(resource)
|
||||
resource.update.assert_called_once_with()
|
||||
|
||||
|
||||
class EC2ScenarioTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(EC2ScenarioTestCase, self).setUp()
|
||||
self.server = mock.MagicMock()
|
||||
self.reservation = mock.MagicMock(instances=[self.server])
|
||||
self.res_is = mockpatch.Patch(EC2_UTILS + ".ec2_resource_is")
|
||||
self.update_res = mockpatch.Patch(
|
||||
EC2_UTILS + ".EC2Scenario._update_resource")
|
||||
self.wait_for = mockpatch.Patch(EC2_UTILS + ".bench_utils.wait_for")
|
||||
self.useFixture(self.wait_for)
|
||||
self.useFixture(self.res_is)
|
||||
self.useFixture(self.update_res)
|
||||
self.useFixture(mockpatch.Patch("time.sleep"))
|
||||
|
||||
def _test_atomic_action_timer(self, atomic_actions, name):
|
||||
action_duration = atomic_actions.get(name)
|
||||
self.assertIsNotNone(action_duration)
|
||||
self.assertIsInstance(action_duration, float)
|
||||
|
||||
@mock.patch(EC2_UTILS + ".EC2Scenario.clients")
|
||||
def test__boot_server(self, mock_clients):
|
||||
mock_clients("ec2").run_instances.return_value = self.reservation
|
||||
ec2_scenario = utils.EC2Scenario(context={})
|
||||
return_server = ec2_scenario._boot_server("image", "flavor")
|
||||
expected = mock.call(
|
||||
self.server, is_ready=self.res_is.mock(),
|
||||
update_resource=self.update_res.mock,
|
||||
check_interval=CONF.benchmark.ec2_server_boot_poll_interval,
|
||||
timeout=CONF.benchmark.ec2_server_boot_timeout)
|
||||
self.assertEqual([expected], self.wait_for.mock.mock_calls)
|
||||
self.res_is.mock.assert_has_calls([mock.call("RUNNING")])
|
||||
self.assertEqual(self.wait_for.mock(), return_server)
|
||||
self._test_atomic_action_timer(ec2_scenario.atomic_actions(),
|
||||
"ec2.boot_server")
|
@ -87,6 +87,47 @@ class FlavorResourceTypeTestCase(test.TestCase):
|
||||
resource_config)
|
||||
|
||||
|
||||
class EC2FlavorResourceTypeTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(EC2FlavorResourceTypeTestCase, self).setUp()
|
||||
self.clients = fakes.FakeClients()
|
||||
self.clients.nova().flavors._cache(fakes.FakeResource(name="m1.tiny",
|
||||
id="1"))
|
||||
self.clients.nova().flavors._cache(fakes.FakeResource(name="m1.nano",
|
||||
id="2"))
|
||||
self.clients.nova().flavors._cache(fakes.FakeResource(name="m1.large",
|
||||
id="3"))
|
||||
self.clients.nova().flavors._cache(fakes.FakeResource(name="m1.xlarge",
|
||||
id="3"))
|
||||
|
||||
def test_transform_by_name(self):
|
||||
resource_config = {"name": "m1.nano"}
|
||||
flavor_name = types.EC2FlavorResourceType.transform(
|
||||
clients=self.clients,
|
||||
resource_config=resource_config)
|
||||
self.assertEqual(flavor_name, "m1.nano")
|
||||
|
||||
def test_transform_by_id(self):
|
||||
resource_config = {"id": "2"}
|
||||
flavor_name = types.EC2FlavorResourceType.transform(
|
||||
clients=self.clients,
|
||||
resource_config=resource_config)
|
||||
self.assertEqual(flavor_name, "m1.nano")
|
||||
|
||||
def test_transform_by_id_no_match(self):
|
||||
resource_config = {"id": "4"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2FlavorResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_id_multiple_match(self):
|
||||
resource_config = {"id": "3"}
|
||||
self.assertRaises(exceptions.MultipleMatchesFound,
|
||||
types.EC2FlavorResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
|
||||
class ImageResourceTypeTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -149,6 +190,91 @@ class ImageResourceTypeTestCase(test.TestCase):
|
||||
resource_config)
|
||||
|
||||
|
||||
class EC2ImageResourceTypeTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(EC2ImageResourceTypeTestCase, self).setUp()
|
||||
self.clients = fakes.FakeClients()
|
||||
image1 = fakes.FakeResource(name="cirros-0.3.1-uec", id="100")
|
||||
self.clients.glance().images._cache(image1)
|
||||
image2 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk", id="102")
|
||||
self.clients.glance().images._cache(image2)
|
||||
image3 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk-copy",
|
||||
id="102")
|
||||
self.clients.glance().images._cache(image3)
|
||||
image4 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk-copy",
|
||||
id="103")
|
||||
self.clients.glance().images._cache(image4)
|
||||
|
||||
ec2_image1 = fakes.FakeResource(name="cirros-0.3.1-uec", id="200")
|
||||
ec2_image2 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk",
|
||||
id="201")
|
||||
ec2_image3 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk-copy",
|
||||
id="202")
|
||||
ec2_image4 = fakes.FakeResource(name="cirros-0.3.1-uec-ramdisk-copy",
|
||||
id="203")
|
||||
|
||||
self.clients.ec2().get_all_images = mock.Mock(
|
||||
return_value=[ec2_image1, ec2_image2, ec2_image3, ec2_image4])
|
||||
|
||||
def test_transform_by_name(self):
|
||||
resource_config = {"name": "^cirros-0.3.1-uec$"}
|
||||
ec2_image_id = types.EC2ImageResourceType.transform(
|
||||
clients=self.clients,
|
||||
resource_config=resource_config)
|
||||
self.assertEqual(ec2_image_id, "200")
|
||||
|
||||
def test_transform_by_id(self):
|
||||
resource_config = {"id": "100"}
|
||||
ec2_image_id = types.EC2ImageResourceType.transform(
|
||||
clients=self.clients,
|
||||
resource_config=resource_config)
|
||||
self.assertEqual(ec2_image_id, "200")
|
||||
|
||||
def test_transform_by_id_no_match(self):
|
||||
resource_config = {"id": "101"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_id_match_multiple(self):
|
||||
resource_config = {"id": "102"}
|
||||
self.assertRaises(exceptions.MultipleMatchesFound,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_name_no_match(self):
|
||||
resource_config = {"name": "cirros-0.3.1-uec-boot"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_name_match_multiple(self):
|
||||
resource_config = {"name": "cirros-0.3.1-uec-ramdisk-copy"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_regex(self):
|
||||
resource_config = {"regex": "-uec$"}
|
||||
ec2_image_id = types.EC2ImageResourceType.transform(
|
||||
clients=self.clients,
|
||||
resource_config=resource_config)
|
||||
self.assertEqual(ec2_image_id, "200")
|
||||
|
||||
def test_transform_by_regex_match_multiple(self):
|
||||
resource_config = {"regex": "^cirros"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
def test_transform_by_regex_no_match(self):
|
||||
resource_config = {"regex": "-boot$"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
types.EC2ImageResourceType.transform, self.clients,
|
||||
resource_config)
|
||||
|
||||
|
||||
class VolumeTypeResourceTypeTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -1304,6 +1304,12 @@ class FakeSwiftClient(FakeObjectManager):
|
||||
pass
|
||||
|
||||
|
||||
class FakeEC2Client(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class FakeClients(object):
|
||||
|
||||
def __init__(self, endpoint_=None):
|
||||
@ -1321,6 +1327,7 @@ class FakeClients(object):
|
||||
self._mistral = None
|
||||
self._swift = None
|
||||
self._murano = None
|
||||
self._ec2 = None
|
||||
self._endpoint = endpoint_ or objects.Endpoint(
|
||||
"http://fake.example.org:5000/v2.0/",
|
||||
"fake_username",
|
||||
@ -1400,6 +1407,11 @@ class FakeClients(object):
|
||||
self._murano = FakeMuranoClient()
|
||||
return self._murano
|
||||
|
||||
def ec2(self):
|
||||
if not self._ec2:
|
||||
self._ec2 = FakeEC2Client()
|
||||
return self._ec2
|
||||
|
||||
|
||||
class FakeRunner(object):
|
||||
|
||||
|
@ -320,6 +320,32 @@ class OSClientsTestCase(test.TestCase):
|
||||
mock_swift.Connection.assert_called_once_with(**kw)
|
||||
self.assertEqual(self.clients.cache["swift"], fake_swift)
|
||||
|
||||
def test_ec2(self):
|
||||
mock_boto = mock.Mock()
|
||||
self.service_catalog.url_for.return_value = "http://fake.to:1/fake"
|
||||
self.fake_keystone.ec2 = mock.Mock()
|
||||
self.fake_keystone.ec2.create.return_value = mock.Mock(
|
||||
access="fake_access", secret="fake_secret")
|
||||
fake_ec2 = fakes.FakeEC2Client()
|
||||
mock_boto.connect_ec2_endpoint.return_value = fake_ec2
|
||||
|
||||
self.assertNotIn("ec2", self.clients.cache)
|
||||
with mock.patch.dict("sys.modules", {"boto": mock_boto}):
|
||||
client = self.clients.ec2()
|
||||
self.assertEqual(fake_ec2, client)
|
||||
self.service_catalog.url_for.assert_called_once_with(
|
||||
service_type="ec2",
|
||||
endpoint_type=consts.EndpointType.PUBLIC,
|
||||
region_name=self.endpoint.region_name)
|
||||
kw = {
|
||||
"url": "http://fake.to:1/fake",
|
||||
"aws_access_key_id": "fake_access",
|
||||
"aws_secret_access_key": "fake_secret",
|
||||
"is_secure": cfg.CONF.https_insecure,
|
||||
}
|
||||
mock_boto.connect_ec2_endpoint.assert_called_once_with(**kw)
|
||||
self.assertEqual(fake_ec2, self.clients.cache["ec2"])
|
||||
|
||||
@mock.patch("rally.osclients.Clients.keystone")
|
||||
def test_services(self, mock_keystone):
|
||||
available_services = {consts.ServiceType.IDENTITY: {},
|
||||
|
Loading…
x
Reference in New Issue
Block a user