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:
Wataru Takase 2015-01-15 16:07:00 +01:00
parent 13de04f09c
commit e6199e2f07
20 changed files with 734 additions and 8 deletions

View File

@ -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

View File

@ -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

View File

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

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

View 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

View File

@ -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

View File

@ -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):

View File

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

View File

@ -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.

View File

@ -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

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

View 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

View File

@ -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):

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

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

View File

@ -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):

View File

@ -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):

View File

@ -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: {},