From fcdb11b26ef2623a662bfc346d124c17da423350 Mon Sep 17 00:00:00 2001 From: Andrey Pavlov Date: Tue, 7 Apr 2015 21:05:22 +0300 Subject: [PATCH] upgrade rally scenarios install botocore for rally run increase parameters add scenarios Change-Id: I1b5d2a99ccc9de9cd164f40d4a84b29a8864c7c2 --- ec2api/tests/functional/botocoreclient.py | 6 + rally-scenarios/ec2-api-fakevirt.yaml | 85 +++-- rally-scenarios/plugins/context_plugin_ec2.py | 55 ---- .../plugins/context_plugin_ec2_creds.py | 90 ++++++ .../plugins/context_plugin_ec2_servers.py | 292 ++++++++++++++++++ rally-scenarios/plugins/ec2api_plugin.py | 39 ++- rally-scenarios/post_test_hook.sh | 3 + 7 files changed, 487 insertions(+), 83 deletions(-) delete mode 100644 rally-scenarios/plugins/context_plugin_ec2.py create mode 100644 rally-scenarios/plugins/context_plugin_ec2_creds.py create mode 100644 rally-scenarios/plugins/context_plugin_ec2_servers.py create mode 100755 rally-scenarios/post_test_hook.sh diff --git a/ec2api/tests/functional/botocoreclient.py b/ec2api/tests/functional/botocoreclient.py index 0ddb992f..49baa296 100644 --- a/ec2api/tests/functional/botocoreclient.py +++ b/ec2api/tests/functional/botocoreclient.py @@ -52,10 +52,16 @@ class BotocoreClientBase(object): class APIClientEC2(BotocoreClientBase): + url = None + def __init__(self, url, region, access, secret, *args, **kwargs): super(APIClientEC2, self).__init__(region, access, secret, *args, **kwargs) + self.url = url self.service = self.session.get_service('ec2') self.endpoint = self.service.get_endpoint( region_name=self.region, endpoint_url=url) + + def get_url(self): + return self.url diff --git a/rally-scenarios/ec2-api-fakevirt.yaml b/rally-scenarios/ec2-api-fakevirt.yaml index ae4a705c..d7709b7e 100644 --- a/rally-scenarios/ec2-api-fakevirt.yaml +++ b/rally-scenarios/ec2-api-fakevirt.yaml @@ -1,23 +1,5 @@ --- - EC2APIPlugin.describe_instances: - - - runner: - type: "constant" - times: 2 - concurrency: 1 - context: - users: - tenants: 1 - users_per_tenant: 1 - servers: - flavor: - name: "m1.nano" - image: - name: "^cirros.*uec$" - servers_per_tenant: 1 - prepare_ec2_client: - - EC2APIPlugin.describe_regions: + EC2APIPlugin.describe_addresses_and_instances: - runner: type: "constant" @@ -28,19 +10,74 @@ tenants: 1 users_per_tenant: 1 prepare_ec2_client: - - EC2APIPlugin.describe_images: + ec2_servers: + flavor: "m1.nano" + image: "*cirros*" + servers_per_tenant: 200 + run_in_vpc: False + assign_floating_ip: False + build_timeout: 150 - runner: type: "constant" - times: 20 + times: 10 concurrency: 1 context: users: tenants: 1 users_per_tenant: 1 + prepare_ec2_client: + ec2_servers: + flavor: "m1.nano" + image: "*cirros*" + servers_per_tenant: 200 + run_in_vpc: False + assign_floating_ip: True + build_timeout: 150 + - + runner: + type: "constant" + times: 10 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + prepare_ec2_client: + ec2_servers: + flavor: "m1.nano" + image: "*cirros*" + servers_per_tenant: 200 + run_in_vpc: True + assign_floating_ip: True + build_timeout: 150 + + + EC2APIPlugin.describe_regions: + - + runner: + type: "constant" + times: 100 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + prepare_ec2_client: + + + EC2APIPlugin.describe_images: + - + runner: + type: "constant" + times: 10 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + prepare_ec2_client: fake_images: disk_format: "ami" container_format: "ami" - images_per_tenant: 1000 - prepare_ec2_client: + images_per_tenant: 2000 diff --git a/rally-scenarios/plugins/context_plugin_ec2.py b/rally-scenarios/plugins/context_plugin_ec2.py deleted file mode 100644 index 7012744d..00000000 --- a/rally-scenarios/plugins/context_plugin_ec2.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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. - -from rally.benchmark.context import base -from rally.common import log as logging -from rally import osclients - -LOG = logging.getLogger(__name__) - - -@base.context(name="prepare_ec2_client", order=1000) -class PrepareEC2ClientContext(base.Context): - - def setup(self): - """This method is called before the task start.""" - try: - user = self.context['users'][0] - osclient = osclients.Clients(user['endpoint']) - keystone = osclient.keystone() - creds = keystone.ec2.list(user['id']) - if not creds: - creds = keystone.ec2.create(user['id'], user['tenant_id']) - else: - creds = creds[0] - url = keystone.service_catalog.url_for(service_type='ec2') - url_parts = url.rpartition(':') - nova_url = url_parts[0] + ':8773/' + url_parts[2].partition('/')[2] - self.context['users'][0]['ec2args'] = { - 'region': 'RegionOne', - 'url': url, - 'nova_url': nova_url, - 'access': creds.access, - 'secret': creds.secret - } - except Exception as e: - msg = "Can't prepare ec2 client: %s" % e.message - if logging.is_debug(): - LOG.exception(msg) - else: - LOG.warning(msg) - - def cleanup(self): - pass diff --git a/rally-scenarios/plugins/context_plugin_ec2_creds.py b/rally-scenarios/plugins/context_plugin_ec2_creds.py new file mode 100644 index 00000000..55163698 --- /dev/null +++ b/rally-scenarios/plugins/context_plugin_ec2_creds.py @@ -0,0 +1,90 @@ +# 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. + +from rally.benchmark.context import base +from rally.benchmark.wrappers import network as network_wrapper +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 + +LOG = logging.getLogger(__name__) + + +@base.context(name="prepare_ec2_client", order=110) +class PrepareEC2ClientContext(base.Context): + + def __init__(self, context): + super(PrepareEC2ClientContext, self).__init__(context) + self.net_wrapper = network_wrapper.wrap( + osclients.Clients(context["admin"]["endpoint"]), + self.config) + self.net_wrapper.start_cidr = '10.0.0.0/16' + + @rutils.log_task_wrapper(LOG.info, _("Enter context: `EC2 creds`")) + def setup(self): + """This method is called before the task start.""" + try: + for user in self.context['users']: + osclient = osclients.Clients(user['endpoint']) + keystone = osclient.keystone() + creds = keystone.ec2.list(user['id']) + if not creds: + creds = keystone.ec2.create(user['id'], user['tenant_id']) + else: + creds = creds[0] + url = keystone.service_catalog.url_for(service_type='ec2') + url_parts = url.rpartition(':') + nova_url = (url_parts[0] + ':8773/' + + url_parts[2].partition('/')[2]) + self.context['users'][0]['ec2args'] = { + 'region': 'RegionOne', + 'url': url, + 'nova_url': nova_url, + 'access': creds.access, + 'secret': creds.secret + } + + if self.net_wrapper.SERVICE_IMPL == consts.Service.NEUTRON: + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + body = {"quota": {"router": -1, "floatingip": -1}} + self.net_wrapper.client.update_quota(tenant_id, body) + network = self.net_wrapper.create_network( + tenant_id, add_router=True, subnets_num=1) + self.context["tenants"][tenant_id]["network"] = network + + except Exception as e: + msg = "Can't prepare ec2 client: %s" % e.message + if logging.is_debug(): + LOG.exception(msg) + else: + LOG.warning(msg) + + @rutils.log_task_wrapper(LOG.info, _("Exit context: `EC2 creds`")) + def cleanup(self): + try: + if self.net_wrapper.SERVICE_IMPL == consts.Service.NEUTRON: + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + network = self.context["tenants"][tenant_id]["network"] + self.net_wrapper.delete_network(network) + except Exception as e: + msg = "Can't cleanup ec2 client: %s" % e.message + if logging.is_debug(): + LOG.exception(msg) + else: + LOG.warning(msg) diff --git a/rally-scenarios/plugins/context_plugin_ec2_servers.py b/rally-scenarios/plugins/context_plugin_ec2_servers.py new file mode 100644 index 00000000..24941a39 --- /dev/null +++ b/rally-scenarios/plugins/context_plugin_ec2_servers.py @@ -0,0 +1,292 @@ +# 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 rally.benchmark.context import base +from rally.common.i18n import _ +from rally.common import log as logging +from rally.common import utils as rutils +from rally import consts + +from ec2api.tests.functional import base as ec2_tests_base +from ec2api.tests.functional import botocoreclient + + +LOG = logging.getLogger(__name__) + + +@base.context(name="ec2_servers", order=450) +class FakeServerGenerator(base.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": "string", + }, + "flavor": { + "type": "string" + }, + "servers_per_tenant": { + "type": "integer", + "minimum": 1 + }, + "run_in_vpc": { + "type": "boolean" + }, + "assign_floating_ip": { + "type": "boolean" + }, + "build_timeout": { + "type": "integer", + "minimum": 30 + }, + "servers_per_run": { + "type": "integer", + "minimum": 1 + } + }, + "required": ["image", "flavor"], + "additionalProperties": False + } + + DEFAULT_CONFIG = { + "servers_per_tenant": 5, + "build_timeout": 30, + "servers_per_run": 10 + } + + CIDR = "10.0.0.0/16" + AWS_ZONE = "nova" + + @rutils.log_task_wrapper(LOG.info, _("Enter context: `EC2 Servers`")) + def setup(self): + image = self.config["image"] + image_id = None + + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + LOG.info("Booting servers for user tenant %s " + % (user["tenant_id"])) + + args = user['ec2args'] + client = botocoreclient.APIClientEC2( + args['url'], args['region'], args['access'], args['secret']) + + if image_id is None: + resp, data = client.DescribeImages( + Filters=[{'Name': 'name', 'Values': [image]}, + {'Name': 'image-type', 'Values': ['machine']}]) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + image_id = data['Images'][0]['ImageId'] + + self.context["tenants"][tenant_id]["servers"] = list() + self.context["tenants"][tenant_id]["networks"] = list() + self.run_instances(tenant_id, client, image_id) + + LOG.info("waiting for running state") + ids = self.context["tenants"][tenant_id]["servers"] + start_time = time.time() + while True: + resp, data = client.DescribeInstances(InstanceIds=ids) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + for instance in data['Reservations'][0]['Instances']: + assert 'error' != instance['State']['Name'] + if instance['State']['Name'] != 'running': + break + else: + break + time.sleep(5) + dtime = time.time() - start_time + assert dtime <= self.config["build_timeout"] + LOG.info("end of waiting") + + self.context["tenants"][tenant_id]["addresses"] = list() + if self.config.get('assign_floating_ip', False): + LOG.info("assign floating ips") + for instance_id in ids: + self.assign_floating_ip(tenant_id, client, instance_id) + + def run_instances(self, tenant_id, client, image_id): + flavor = self.config["flavor"] + servers_per_tenant = self.config["servers_per_tenant"] + LOG.info("Calling run_instance with image_id=%s " + "flavor=%s servers_per_tenant=%s" + % (image_id, flavor, servers_per_tenant)) + + servers_per_run = self.config["servers_per_run"] + while servers_per_tenant > 0: + if servers_per_tenant < servers_per_run: + servers_per_run = servers_per_tenant + kwargs = {"ImageId": image_id, "InstanceType": flavor, + "MinCount": servers_per_run, "MaxCount": servers_per_run} + if self.config.get("run_in_vpc", False): + subnet_id = self.prepare_network(tenant_id, client) + kwargs["SubnetId"] = subnet_id + resp, data = client.RunInstances(*[], **kwargs) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + ids = [s['InstanceId'] for s in data['Instances']] + self.context["tenants"][tenant_id]["servers"] += ids + servers_per_tenant -= servers_per_run + + def prepare_network(self, tenant_id, client): + result = dict() + self.context["tenants"][tenant_id]["networks"].append(result) + + resp, data = client.CreateVpc(CidrBlock=self.CIDR) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + vpc_id = data['Vpc']['VpcId'] + result["vpc_id"] = vpc_id + resp, data = client.CreateSubnet(VpcId=vpc_id, + CidrBlock=self.CIDR, AvailabilityZone=self.AWS_ZONE) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + subnet_id = data['Subnet']['SubnetId'] + result["subnet_id"] = subnet_id + + resp, data = client.CreateInternetGateway() + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + gw_id = data['InternetGateway']['InternetGatewayId'] + result["gw_id"] = gw_id + resp, data = client.AttachInternetGateway(VpcId=vpc_id, + InternetGatewayId=gw_id) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + assert 200 == resp.status_code + + return subnet_id + + def assign_floating_ip(self, tenant_id, client, instance_id): + is_vpc = self.config.get("run_in_vpc", False) + + kwargs = dict() + if is_vpc: + kwargs['Domain'] = 'vpc' + resp, data = client.AllocateAddress(*[], **kwargs) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + return + alloc_id = data.get('AllocationId') + public_ip = data['PublicIp'] + if is_vpc: + self.context["tenants"][tenant_id]["addresses"].append( + {'AllocationId': alloc_id}) + else: + self.context["tenants"][tenant_id]["addresses"].append( + {'PublicIp': public_ip}) + + kwargs = {'InstanceId': instance_id} + if is_vpc: + kwargs['AllocationId'] = alloc_id + else: + kwargs['PublicIp'] = public_ip + resp, data = client.AssociateAddress(*[], **kwargs) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + if is_vpc: + resp, data = client.ReleaseAddress(AllocationId=alloc_id) + else: + resp, data = client.ReleaseAddress(PublicIp=public_ip) + if resp.status_code != 200: + LOG.error(ec2_tests_base.EC2ErrorConverter(data)) + + @rutils.log_task_wrapper(LOG.info, _("Exit context: `EC2 Servers`")) + def cleanup(self): + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + args = user['ec2args'] + client = botocoreclient.APIClientEC2( + args['url'], args['region'], args['access'], args['secret']) + ids = self.context["tenants"][tenant_id].get("servers", []) + + servers_per_run = self.config["servers_per_run"] + mod = len(ids) / servers_per_run + for i in xrange(0, mod): + part_ids = ids[i * servers_per_run:(i + 1) * servers_per_run] + resp, data = client.TerminateInstances(InstanceIds=part_ids) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + part_ids = ids[mod * servers_per_run:] + if part_ids: + resp, data = client.TerminateInstances(InstanceIds=part_ids) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + + start_time = time.time() + while True: + resp, data = client.DescribeInstances(InstanceIds=ids) + if (resp.status_code == 400 + or len(data['Reservations']) == 0 + or len(data['Reservations'][0]['Instances']) == 0): + break + for instance in data['Reservations'][0]['Instances']: + assert 'error' != instance['State']['Name'] + if instance['State']['Name'] != 'terminated': + break + else: + break + time.sleep(5) + dtime = time.time() - start_time + assert dtime <= self.config["build_timeout"] + + LOG.info("Cleanup addresses") + kwargss = self.context["tenants"][tenant_id].get("addresses", []) + for kwargs in kwargss: + resp, data = client.ReleaseAddress(*[], **kwargs) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + + LOG.info("Cleanup networks") + networks = self.context["tenants"][tenant_id].get("networks", []) + for network in networks: + vpc_id = network.get("vpc_id") + subnet_id = network.get("subnet_id") + gw_id = network.get("gw_id") + if gw_id: + resp, data = client.DetachInternetGateway( + VpcId=vpc_id, InternetGatewayId=gw_id) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + time.sleep(1) + resp, data = client.DeleteInternetGateway( + InternetGatewayId=gw_id) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + time.sleep(1) + if subnet_id: + resp, data = client.DeleteSubnet(SubnetId=subnet_id) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) + time.sleep(1) + if vpc_id: + resp, data = client.DeleteVpc(VpcId=vpc_id) + if resp.status_code != 200: + LOG.warning(ec2_tests_base.EC2ErrorConverter(data)) diff --git a/rally-scenarios/plugins/ec2api_plugin.py b/rally-scenarios/plugins/ec2api_plugin.py index c2af09f5..6d338459 100644 --- a/rally-scenarios/plugins/ec2api_plugin.py +++ b/rally-scenarios/plugins/ec2api_plugin.py @@ -21,7 +21,6 @@ LOG = logging.getLogger(__name__) class EC2APIPlugin(base.Scenario): - """Plugin which lists instances.""" def _get_client(self, is_nova): args = self.context['user']['ec2args'] @@ -31,12 +30,12 @@ class EC2APIPlugin(base.Scenario): return client def _run(self, base_name, func): - client = self._get_client(False) - with base.AtomicAction(self, base_name + '_ec2api'): - func(self, client) client = self._get_client(True) with base.AtomicAction(self, base_name + '_nova'): func(self, client) + client = self._get_client(False) + with base.AtomicAction(self, base_name + '_ec2api'): + func(self, client) def _both_api_runner(): def wrap(func): @@ -52,6 +51,12 @@ class EC2APIPlugin(base.Scenario): resp, data = client.DescribeInstances() assert 200 == resp.status_code + @base.scenario() + @_both_api_runner() + def describe_addresses(self, client): + resp, data = client.DescribeAddresses() + assert 200 == resp.status_code + @base.scenario() @_both_api_runner() def describe_regions(self, client): @@ -63,3 +68,29 @@ class EC2APIPlugin(base.Scenario): def describe_images(self, client): resp, data = client.DescribeImages() assert 200 == resp.status_code + + _instance_id_by_client = dict() + + @base.scenario() + @_both_api_runner() + def describe_one_instance(self, client): + client_id = client.get_url() + instance_id = self._instance_id_by_client.get(client_id) + if not instance_id: + resp, data = client.DescribeInstances() + assert 200 == resp.status_code + instances = data['Reservations'][0]['Instances'] + index = len(instances) / 3 + instance_id = instances[index]['InstanceId'] + self._instance_id_by_client[client_id] = instance_id + LOG.info("found instance = %s for client %s" + % (instance_id, client_id)) + + resp, data = client.DescribeInstances(InstanceIds=[instance_id]) + assert 200 == resp.status_code + + @base.scenario() + def describe_addresses_and_instances(self): + self.describe_addresses() + self.describe_instances() + self.describe_one_instance() diff --git a/rally-scenarios/post_test_hook.sh b/rally-scenarios/post_test_hook.sh new file mode 100755 index 00000000..778e9f8b --- /dev/null +++ b/rally-scenarios/post_test_hook.sh @@ -0,0 +1,3 @@ +#!/bin/bash -x + +sudo pip install botocore