From 07bbc3094d9b8838bdebcb42dbcc43cc515bc582 Mon Sep 17 00:00:00 2001 From: Mikhail Dubov <mdubov@mirantis.com> Date: Mon, 13 Oct 2014 12:28:45 +0400 Subject: [PATCH] Cover Rally with docstrings & Test this coverage Rally should have detailed docstrings for: * Benchmark scenario classes * Benchmark scenarios * Deploy engines * Server providers * SLA Here we add such docstrings and also add a test suite that checks that Rally is 100% covered with docstrings and that these docstrings are correctly formed. We also change the interface of the following benchmark scenarios (for the sake of unification): * CinderVolumes.create_and_attach_volume * CinderVolumes.create_snapshot_and_attach_volume * CinderVolumes.create_nested_snapshots_and_attach_volume Finally, we refactor a bit NovaServers.boot_and_bounce_server. Change-Id: Ia38c8fc2d692a09719d3e068d332647d4b0da47f --- .../plugins/scenario/scenario_plugin.py | 3 +- .../cinder/create-and-attach-volume.json | 2 +- .../cinder/create-and-attach-volume.yaml | 2 +- .../create-snapshot-and-attach-volume.json | 4 +- .../create-snapshot-and-attach-volume.yaml | 4 +- ...te_nested_snapshots_and_attach_volume.json | 2 +- ...te_nested_snapshots_and_attach_volume.yaml | 2 +- rally-jobs/plugins/fake_plugin.py | 5 + rally-jobs/rally.yaml | 8 +- rally/aas/rest/app.py | 3 +- .../scenarios/authenticate/authenticate.py | 6 +- .../benchmark/scenarios/ceilometer/alarms.py | 20 ++- .../benchmark/scenarios/ceilometer/meters.py | 3 +- .../benchmark/scenarios/ceilometer/queries.py | 16 +- .../scenarios/ceilometer/resources.py | 3 +- rally/benchmark/scenarios/ceilometer/stats.py | 5 +- rally/benchmark/scenarios/ceilometer/utils.py | 23 ++- rally/benchmark/scenarios/cinder/utils.py | 9 +- rally/benchmark/scenarios/cinder/volumes.py | 160 +++++++++++------- rally/benchmark/scenarios/designate/basic.py | 24 +-- rally/benchmark/scenarios/designate/utils.py | 26 +-- rally/benchmark/scenarios/dummy/dummy.py | 38 ++++- rally/benchmark/scenarios/glance/images.py | 37 +++- rally/benchmark/scenarios/glance/utils.py | 21 ++- rally/benchmark/scenarios/heat/stacks.py | 21 +-- rally/benchmark/scenarios/heat/utils.py | 5 +- rally/benchmark/scenarios/keystone/basic.py | 36 ++++ rally/benchmark/scenarios/keystone/utils.py | 15 +- rally/benchmark/scenarios/neutron/network.py | 46 +++-- rally/benchmark/scenarios/neutron/utils.py | 2 +- .../scenarios/nova/security_group.py | 7 +- rally/benchmark/scenarios/nova/servers.py | 124 +++++++++++--- rally/benchmark/scenarios/nova/utils.py | 37 ++-- rally/benchmark/scenarios/quotas/quotas.py | 9 +- rally/benchmark/scenarios/quotas/utils.py | 5 +- .../scenarios/requests/http_requests.py | 4 +- rally/benchmark/scenarios/sahara/clusters.py | 117 +++++++------ rally/benchmark/scenarios/sahara/jobs.py | 29 ++-- .../scenarios/sahara/node_group_templates.py | 25 +-- rally/benchmark/scenarios/sahara/utils.py | 96 +++++------ rally/benchmark/scenarios/tempest/tempest.py | 12 +- rally/benchmark/scenarios/vm/utils.py | 10 +- rally/benchmark/scenarios/vm/vmtasks.py | 16 +- rally/benchmark/scenarios/zaqar/basic.py | 12 +- rally/benchmark/scenarios/zaqar/utils.py | 18 +- rally/sshutils.py | 4 +- rally/utils.py | 6 +- .../benchmark/scenarios/nova/test_servers.py | 12 +- tests/unit/benchmark/sla/test_base.py | 1 + .../deploy/serverprovider/test_provider.py | 12 ++ tests/unit/deploy/test_engine.py | 16 ++ tests/unit/fakes.py | 1 + tests/unit/test_docstrings.py | 102 +++++++++++ 53 files changed, 795 insertions(+), 431 deletions(-) create mode 100644 tests/unit/test_docstrings.py diff --git a/doc/samples/plugins/scenario/scenario_plugin.py b/doc/samples/plugins/scenario/scenario_plugin.py index 556c24d7aa..2f56fc4b21 100644 --- a/doc/samples/plugins/scenario/scenario_plugin.py +++ b/doc/samples/plugins/scenario/scenario_plugin.py @@ -2,7 +2,7 @@ from rally.benchmark.scenarios import base class ScenarioPlugin(base.Scenario): - """Sample of plugin which lists flavors""" + """Sample plugin which lists flavors.""" @base.atomic_action_timer("list_flavors") def _list_flavors(self): @@ -19,5 +19,6 @@ class ScenarioPlugin(base.Scenario): @base.scenario() def list_flavors(self): + """List flavors.""" self._list_flavors() self._list_flavors_as_admin() diff --git a/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.json b/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.json index 4d1c1637a3..b7049f346a 100644 --- a/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.json +++ b/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.json @@ -2,7 +2,7 @@ "CinderVolumes.create_and_attach_volume": [ { "args": { - "volume_size": 10, + "size": 10, "image": { "name": "^cirros.*uec$" }, diff --git a/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.yaml b/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.yaml index 38af4e9c90..62410e3186 100644 --- a/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.yaml +++ b/doc/samples/tasks/scenarios/cinder/create-and-attach-volume.yaml @@ -2,7 +2,7 @@ CinderVolumes.create_and_attach_volume: - args: - volume_size: 10 + size: 10 image: name: "^cirros.*uec$" flavor: diff --git a/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.json b/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.json index 703391f365..1af3950e64 100644 --- a/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.json +++ b/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.json @@ -3,7 +3,7 @@ { "args": { "volume_type": false, - "volume_size": { + "size": { "min": 1, "max": 5 } @@ -32,7 +32,7 @@ { "args": { "volume_type": true, - "volume_size": { + "size": { "min": 1, "max": 5 } diff --git a/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.yaml b/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.yaml index 42eba6de52..c77f28750f 100644 --- a/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.yaml +++ b/doc/samples/tasks/scenarios/cinder/create-snapshot-and-attach-volume.yaml @@ -3,7 +3,7 @@ - args: volume_type: false - volume_size: + size: min: 1 max: 5 runner: @@ -23,7 +23,7 @@ - args: volume_type: true - volume_size: + size: min: 1 max: 5 runner: diff --git a/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.json b/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.json index 69d1538993..3bbe8c39da 100644 --- a/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.json +++ b/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.json @@ -2,7 +2,7 @@ "CinderVolumes.create_nested_snapshots_and_attach_volume": [ { "args": { - "volume_size": { + "size": { "min": 1, "max": 5 }, diff --git a/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.yaml b/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.yaml index d58b549e76..00291cad5b 100644 --- a/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.yaml +++ b/doc/samples/tasks/scenarios/cinder/create_nested_snapshots_and_attach_volume.yaml @@ -2,7 +2,7 @@ CinderVolumes.create_nested_snapshots_and_attach_volume: - args: - volume_size: + size: min: 1 max: 5 nested_level: diff --git a/rally-jobs/plugins/fake_plugin.py b/rally-jobs/plugins/fake_plugin.py index 378c2dbbd6..c23137f9b8 100644 --- a/rally-jobs/plugins/fake_plugin.py +++ b/rally-jobs/plugins/fake_plugin.py @@ -21,6 +21,7 @@ from rally.benchmark.scenarios import base class FakePlugin(base.Scenario): + """Fake plugin with a scenario.""" @base.atomic_action_timer("test1") def _test1(self, factor): @@ -32,5 +33,9 @@ class FakePlugin(base.Scenario): @base.scenario() def testplugin(self, factor=1): + """Fake scenario. + + :param factor: influences the argument value for a time.sleep() call + """ self._test1(factor) self._test2(factor) diff --git a/rally-jobs/rally.yaml b/rally-jobs/rally.yaml index 6194fe3818..91f0c1c4bd 100755 --- a/rally-jobs/rally.yaml +++ b/rally-jobs/rally.yaml @@ -734,7 +734,7 @@ CinderVolumes.create_and_attach_volume: - args: - volume_size: 1 + size: 1 image: name: "^cirros.*uec$" flavor: @@ -755,7 +755,7 @@ - args: volume_type: false - volume_size: + size: min: 1 max: 2 runner: @@ -778,7 +778,7 @@ - args: volume_type: true - volume_size: + size: min: 1 max: 2 runner: @@ -803,7 +803,7 @@ CinderVolumes.create_nested_snapshots_and_attach_volume: - args: - volume_size: + size: min: 1 max: 2 nested_level: diff --git a/rally/aas/rest/app.py b/rally/aas/rest/app.py index 011d80d963..a8873c9945 100644 --- a/rally/aas/rest/app.py +++ b/rally/aas/rest/app.py @@ -24,7 +24,7 @@ def setup_app(config): This is a generic interface method of an application. :param config: An instance of :class:`pecan.Config`. - :return: A normal WSGI application, an instance of + :returns: A normal WSGI application, an instance of :class:`pecan.Pecan`. """ app = pecan.Pecan(config.app.root, debug=logging.is_debug()) @@ -32,6 +32,7 @@ def setup_app(config): def make_app(): + """Load Pecan application.""" config = { "app": { "root": "rally.aas.rest.controllers.root.RootController", diff --git a/rally/benchmark/scenarios/authenticate/authenticate.py b/rally/benchmark/scenarios/authenticate/authenticate.py index a308227e7a..2f7090e72f 100644 --- a/rally/benchmark/scenarios/authenticate/authenticate.py +++ b/rally/benchmark/scenarios/authenticate/authenticate.py @@ -17,14 +17,16 @@ from rally.benchmark import validation class Authenticate(base.Scenario): - """This class should contain authentication mechanism. + """Benchmark scenarios for the authentication mechanism. - For different types of clients like Keystone. + Benchmark scenarios for different types of OpenStack clients like Keystone, + Nova etc. """ @validation.required_openstack(users=True) @base.scenario() def keystone(self): + """Check Keystone Client.""" self.clients("keystone") @validation.number("repetitions", minval=1) diff --git a/rally/benchmark/scenarios/ceilometer/alarms.py b/rally/benchmark/scenarios/ceilometer/alarms.py index 7a259bfc29..4febd7d04a 100644 --- a/rally/benchmark/scenarios/ceilometer/alarms.py +++ b/rally/benchmark/scenarios/ceilometer/alarms.py @@ -19,17 +19,19 @@ from rally import consts class CeilometerAlarms(ceilometerutils.CeilometerScenario): + """Benchmark scenarios for Ceilometer Alarms API.""" @validation.required_services(consts.Service.CEILOMETER) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_alarm(self, meter_name, threshold, **kwargs): - """Test creating an alarm. + """Create an alarm. This scenarios test POST /v2/alarms. meter_name and threshold are required parameters for alarm creation. kwargs stores other optional parameters like 'ok_actions', - 'project_id' etc that may be passed while creating alarm. + 'project_id' etc that may be passed while creating an alarm. + :param meter_name: specifies meter name of the alarm :param threshold: specifies alarm threshold :param kwargs: specifies optional arguments for alarm creation. @@ -40,7 +42,7 @@ class CeilometerAlarms(ceilometerutils.CeilometerScenario): @validation.required_openstack(users=True) @base.scenario() def list_alarms(self): - """Test fetching all alarms. + """Fetch all alarms. This scenario fetches list of all alarms using GET /v2/alarms. """ @@ -50,13 +52,15 @@ class CeilometerAlarms(ceilometerutils.CeilometerScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_and_list_alarm(self, meter_name, threshold, **kwargs): - """Test creating and getting newly created alarm. + """Create and get the newly created alarm. This scenarios test GET /v2/alarms/(alarm_id) Initially alarm is created and then the created alarm is fetched using its alarm_id. meter_name and threshold are required parameters for alarm creation. kwargs stores other optional parameters like - 'ok_actions', 'project_id' etc. that may be passed while creating alarm + 'ok_actions', 'project_id' etc. that may be passed while creating + an alarm. + :param meter_name: specifies meter name of the alarm :param threshold: specifies alarm threshold :param kwargs: specifies optional arguments for alarm creation. @@ -68,13 +72,14 @@ class CeilometerAlarms(ceilometerutils.CeilometerScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_and_update_alarm(self, meter_name, threshold, **kwargs): - """Test creating and updating the newly created alarm. + """Create and update the newly created alarm. This scenarios test PUT /v2/alarms/(alarm_id) Initially alarm is created and then the created alarm is updated using its alarm_id. meter_name and threshold are required parameters for alarm creation. kwargs stores other optional parameters like 'ok_actions', 'project_id' etc that may be passed while alarm creation. + :param meter_name: specifies meter name of the alarm :param threshold: specifies alarm threshold :param kwargs: specifies optional arguments for alarm creation. @@ -87,13 +92,14 @@ class CeilometerAlarms(ceilometerutils.CeilometerScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_and_delete_alarm(self, meter_name, threshold, **kwargs): - """Test creating and deleting the newly created alarm. + """Create and delete the newly created alarm. This scenarios test DELETE /v2/alarms/(alarm_id) Initially alarm is created and then the created alarm is deleted using its alarm_id. meter_name and threshold are required parameters for alarm creation. kwargs stores other optional parameters like 'ok_actions', 'project_id' etc that may be passed while alarm creation. + :param meter_name: specifies meter name of the alarm :param threshold: specifies alarm threshold :param kwargs: specifies optional arguments for alarm creation. diff --git a/rally/benchmark/scenarios/ceilometer/meters.py b/rally/benchmark/scenarios/ceilometer/meters.py index 2667e1b0b8..d6f23c20e8 100644 --- a/rally/benchmark/scenarios/ceilometer/meters.py +++ b/rally/benchmark/scenarios/ceilometer/meters.py @@ -19,10 +19,11 @@ from rally import consts class CeilometerMeters(ceilometerutils.CeilometerScenario): + """Benchmark scenarios for Ceilometer Meters API.""" @validation.required_services(consts.Service.CEILOMETER) @validation.required_openstack(users=True) @base.scenario() def list_meters(self): - """Test fetching user's meters.""" + """Fetch user's meters.""" self._list_meters() diff --git a/rally/benchmark/scenarios/ceilometer/queries.py b/rally/benchmark/scenarios/ceilometer/queries.py index df6d69d44c..372523c4ea 100644 --- a/rally/benchmark/scenarios/ceilometer/queries.py +++ b/rally/benchmark/scenarios/ceilometer/queries.py @@ -21,16 +21,18 @@ from rally import consts class CeilometerQueries(ceilometerutils.CeilometerScenario): + """Benchmark scenarios for Ceilometer Queries API.""" @validation.required_services(consts.Service.CEILOMETER) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_and_query_alarms(self, meter_name, threshold, filter=None, orderby=None, limit=None, **kwargs): - """Creates an alarm and then queries it with specific parameters. + """Create an alarm and then query it with specific parameters. This scenario tests POST /v2/query/alarms - An alarm is first created and then fetched using the input query + An alarm is first created and then fetched using the input query. + :param meter_name: specifies meter name of alarm :param threshold: specifies alarm threshold :param filter: optional filter query dictionary @@ -49,11 +51,12 @@ class CeilometerQueries(ceilometerutils.CeilometerScenario): @base.scenario(context={"cleanup": ["ceilometer"]}) def create_and_query_alarm_history(self, meter_name, threshold, orderby=None, limit=None, **kwargs): - """Creates an alarm and then queries for its history + """Create an alarm and then query for its history. This scenario tests POST /v2/query/alarms/history An alarm is first created and then its alarm_id is used to fetch the - history of that specific alarm + history of that specific alarm. + :param meter_name: specifies meter name of alarm :param threshold: specifies alarm threshold :param orderby: optional param for specifying ordering of results @@ -71,10 +74,11 @@ class CeilometerQueries(ceilometerutils.CeilometerScenario): counter_unit, counter_volume, resource_id, filter=None, orderby=None, limit=None, **kwargs): - """Creates a sample and then queries it with specific parameters + """Create a sample and then query it with specific parameters. This scenario tests POST /v2/query/samples - A sample is first created and then fetched using the input query + A sample is first created and then fetched using the input query. + :param counter_name: specifies name of the counter :param counter_type: specifies type of the counter :param counter_unit: specifies name of the counter diff --git a/rally/benchmark/scenarios/ceilometer/resources.py b/rally/benchmark/scenarios/ceilometer/resources.py index 4c37f85c93..00370e20a8 100644 --- a/rally/benchmark/scenarios/ceilometer/resources.py +++ b/rally/benchmark/scenarios/ceilometer/resources.py @@ -19,12 +19,13 @@ from rally import consts class CeilometerResource(ceilometerutils.CeilometerScenario): + """Benchmark scenarios for Ceilometer Resource API.""" @validation.required_services(consts.Service.CEILOMETER) @validation.required_openstack(users=True) @base.scenario() def list_resources(self): - """Test fetching all resources. + """Fetch all resources. This scenario fetches list of all resources using GET /v2/resources. """ diff --git a/rally/benchmark/scenarios/ceilometer/stats.py b/rally/benchmark/scenarios/ceilometer/stats.py index e733121085..5addff5311 100644 --- a/rally/benchmark/scenarios/ceilometer/stats.py +++ b/rally/benchmark/scenarios/ceilometer/stats.py @@ -19,16 +19,17 @@ from rally import consts class CeilometerStats(utils.CeilometerScenario): + """Benchmark scenarios for Ceilometer Stats API.""" @validation.required_services(consts.Service.CEILOMETER) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["ceilometer"]}) def create_meter_and_get_stats(self, **kwargs): - """Test creating a meter and fetching its statistics. + """Create a meter and fetch its statistics. Meter is first created and then statistics is fetched for the same using GET /v2/meters/(meter_name)/statistics. - :param name_length: length of generated (random) part of meter name + :param kwargs: contains optional arguments to create a meter """ meter = self._create_meter(**kwargs) diff --git a/rally/benchmark/scenarios/ceilometer/utils.py b/rally/benchmark/scenarios/ceilometer/utils.py index 41145bbbb2..687fd1c9cb 100644 --- a/rally/benchmark/scenarios/ceilometer/utils.py +++ b/rally/benchmark/scenarios/ceilometer/utils.py @@ -16,10 +16,12 @@ from rally.benchmark.scenarios import base class CeilometerScenario(base.Scenario): + """Base class for Ceilometer scenarios with basic atomic actions.""" + RESOURCE_NAME_PREFIX = "rally_ceilometer_" def _get_alarm_dict(self, **kwargs): - """Prepares and returns alarm dictionary for creating an alarm. + """Prepare and return an alarm dict for creating an alarm. :param kwargs: optional parameters to create alarm :returns: alarm dictionary used to create an alarm @@ -38,6 +40,7 @@ class CeilometerScenario(base.Scenario): List alarm matching alarm_id. It fetches all alarms if alarm_id is None. + :param alarm_id: specifies id of the alarm :returns: list of alarms """ @@ -63,7 +66,7 @@ class CeilometerScenario(base.Scenario): @base.atomic_action_timer('ceilometer.delete_alarm') def _delete_alarm(self, alarm_id): - """Deletes an alarm. + """Delete an alarm. :param alarm_id: specifies id of the alarm """ @@ -71,7 +74,7 @@ class CeilometerScenario(base.Scenario): @base.atomic_action_timer('ceilometer.update_alarm') def _update_alarm(self, alarm_id, alarm_dict_delta): - """Updates an alarm. + """Update an alarm. :param alarm_id: specifies id of the alarm :param alarm_dict_delta: features of alarm to be updated @@ -116,7 +119,9 @@ class CeilometerScenario(base.Scenario): def _query_alarms(self, filter, orderby, limit): """Query alarms with specific parameters. - If no input params are provided, it returns all the results in database + If no input params are provided, it returns all the results + in the database. + :param limit: optional param for maximum number of results returned :param orderby: optional param for specifying ordering of results :param filter: optional filter query @@ -129,7 +134,9 @@ class CeilometerScenario(base.Scenario): def _query_alarm_history(self, filter, orderby, limit): """Query history of an alarm. - If no input params are provided, it returns all the results in database + If no input params are provided, it returns all the results + in the database. + :param limit: optional param for maximum number of results returned :param orderby: optional param for specifying ordering of results :param filter: optional filter query @@ -141,7 +148,7 @@ class CeilometerScenario(base.Scenario): @base.atomic_action_timer('ceilometer.create_sample') def _create_sample(self, counter_name, counter_type, counter_unit, counter_volume, resource_id, **kwargs): - """Creates a Sample with specified parameters. + """Create a Sample with specified parameters. :param counter_name: specifies name of the counter :param counter_type: specifies type of the counter @@ -162,7 +169,9 @@ class CeilometerScenario(base.Scenario): def _query_samples(self, filter, orderby, limit): """Query samples with specified parameters. - If no input params are provided, it returns all the results in database + If no input params are provided, it returns all the results + in the database. + :param limit: optional param for maximum number of results returned :param orderby: optional param for specifying ordering of results :param filter: optional filter query diff --git a/rally/benchmark/scenarios/cinder/utils.py b/rally/benchmark/scenarios/cinder/utils.py index bc6256054e..ad9ba2b3d5 100644 --- a/rally/benchmark/scenarios/cinder/utils.py +++ b/rally/benchmark/scenarios/cinder/utils.py @@ -49,6 +49,7 @@ CONF.register_opts(cinder_benchmark_opts, group=benchmark_group) class CinderScenario(base.Scenario): + """Base class for Cinder scenarios with basic atomic actions.""" RESOURCE_NAME_PREFIX = "rally_volume_" @@ -60,14 +61,13 @@ class CinderScenario(base.Scenario): @base.atomic_action_timer('cinder.create_volume') def _create_volume(self, size, **kwargs): - """create one volume. + """Create one volume. Returns when the volume is actually created and is in the "Available" state. :param size: int be size of volume in GB - :param **kwargs: Other optional parameters to initialize the volume - + :param kwargs: Other optional parameters to initialize the volume :returns: Created volume object """ kwargs["display_name"] = kwargs.get("display_name", @@ -111,8 +111,7 @@ class CinderScenario(base.Scenario): :param volume_id: volume uuid for creating snapshot :param force: flag to indicate whether to snapshot a volume even if it's attached to an instance - :param **kwargs: Other optional parameters to initialize the volume - + :param kwargs: Other optional parameters to initialize the volume :returns: Created snapshot object """ kwargs["display_name"] = kwargs.get("display_name", diff --git a/rally/benchmark/scenarios/cinder/volumes.py b/rally/benchmark/scenarios/cinder/volumes.py index 62194d09e1..d21dca6f7f 100755 --- a/rally/benchmark/scenarios/cinder/volumes.py +++ b/rally/benchmark/scenarios/cinder/volumes.py @@ -28,23 +28,27 @@ LOG = logging.getLogger(__name__) class CinderVolumes(utils.CinderScenario, nova_utils.NovaScenario): + """Benchmark scenarios for Cinder Volumes.""" @validation.required_services(consts.Service.CINDER) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["cinder"]}) def create_and_list_volume(self, size, detailed=True, **kwargs): - """Tests creating a volume and listing volumes. + """Create a volume and list all volumes. - This scenario is a very useful tool to measure - the "cinder volume-list" command performance. + Measure the "cinder volume-list" command performance. - If you have only 1 user in your context, you will - add 1 volume on every iteration. So you will have more - and more volumes and will be able to measure the - performance of the "cinder volume-list" command depending on - the number of images owned by users. + If you have only 1 user in your context, you will + add 1 volume on every iteration. So you will have more + and more volumes and will be able to measure the + performance of the "cinder volume-list" command depending on + the number of images owned by users. + + :param size: volume size (in GB) + :param detailed: determines whether the volume listing should contain + detailed information about all of them + :param kwargs: optional args to create a volume """ - self._create_volume(size, **kwargs) self._list_volumes(detailed) @@ -53,11 +57,20 @@ class CinderVolumes(utils.CinderScenario, @base.scenario(context={"cleanup": ["cinder"]}) def create_and_delete_volume(self, size, min_sleep=0, max_sleep=0, **kwargs): - """Tests creating and then deleting a volume. + """Create and then delete a volume. - Good for testing a maximal bandwidth of cloud. + Good for testing a maximal bandwidth of cloud. Optional 'min_sleep' + and 'max_sleep' parameters allow the scenario to simulate a pause + between volume creation and deletion (of random duration from + [min_sleep, max_sleep]). + + :param size: volume size (in GB) + :param min_sleep: minimum sleep time between volume creation and + deletion (in seconds) + :param max_sleep: maximum sleep time between volume creation and + deletion (in seconds) + :param kwargs: optional args to create a volume """ - volume = self._create_volume(size, **kwargs) self.sleep_between(min_sleep, max_sleep) self._delete_volume(volume) @@ -66,10 +79,13 @@ class CinderVolumes(utils.CinderScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["cinder"]}) def create_volume(self, size, **kwargs): - """Test creating volumes perfromance. + """Create a volume. Good test to check how influence amount of active volumes on performance of creating new. + + :param size: volume size (in GB) + :param kwargs: optional args to create a volume """ self._create_volume(size, **kwargs) @@ -79,9 +95,21 @@ class CinderVolumes(utils.CinderScenario, @base.scenario(context={"cleanup": ["cinder"]}) def create_and_delete_snapshot(self, force=False, min_sleep=0, max_sleep=0, **kwargs): - """Tests creating and then deleting a volume-snapshot.""" - volume_id = self.context["tenant"]["volume"] + """Create and then delete a volume-snapshot. + Optional 'min_sleep' and 'max_sleep' parameters allow the scenario + to simulate a pause between snapshot creation and deletion + (of random duration from [min_sleep, max_sleep]). + + :param force: when set to True, allows snapshot of a volume when + the volume is attached to an instance + :param min_sleep: minimum sleep time between snapshot creation and + deletion (in seconds) + :param max_sleep: maximum sleep time between snapshot creation and + deletion (in seconds) + :param kwargs: optional args to create a shapshot + """ + volume_id = self.context["tenant"]["volume"] snapshot = self._create_snapshot(volume_id, force=force, **kwargs) self.sleep_between(min_sleep, max_sleep) self._delete_snapshot(snapshot) @@ -92,23 +120,26 @@ class CinderVolumes(utils.CinderScenario, @validation.required_services(consts.Service.NOVA, consts.Service.CINDER) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["cinder", "nova"]}) - def create_and_attach_volume(self, volume_size, image, flavor, - min_sleep=0, max_sleep=0, **kwargs): + def create_and_attach_volume(self, size, image, flavor, **kwargs): + """Create a VM and attach a volume to it. - """Tests creating a VM and attaching a volume. - - Simple test to create a vm and attach a volume, then - detach the volume and cleanup. - - :param volume_size: The size of the volume to create - :param image: The glance image name to use for the vm - :param flavor: the VM flavor name + Simple test to create a VM and attach a volume, then + detach the volume and delete volume/VM. + :param size: volume size (in GB) + :param image: Glance image name to use for the VM + :param flavor: VM flavor name + :param kwargs: optional arguments for VM/volume creation """ + if "volume_size" in kwargs: + import warnings + warnings.warn("'volume_size' argument is deprecated. You should " + "use 'size' instead.") + size = kwargs["volume_size"] server = self._boot_server( self._generate_random_name(), image, flavor, **kwargs) - volume = self._create_volume(volume_size, **kwargs) + volume = self._create_volume(size, **kwargs) self._attach_volume(server, volume) self._detach_volume(server, volume) @@ -121,30 +152,35 @@ class CinderVolumes(utils.CinderScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["cinder", "nova"]}) def create_snapshot_and_attach_volume(self, volume_type=False, - volume_size=None, **kwargs): + size=None, **kwargs): - """Tests volume create, snapshot create and volume attach/detach + """Create volume, snapshot and attach/detach volume. This scenario is based off of the standalone qaStressTest.py (https://github.com/WaltHP/cinder-stress). :param volume_type: Whether or not to specify volume type when creating - volumes. - :param volume_size: Volume size - dictionary, contains two values - min - minimum size volumes will be created as. - max - maximum size volumes will be created as. - default values: {"min": 1, "max": 5} + volumes. + :param size: Volume size - dictionary, contains two values: + min - minimum size volumes will be created as; + max - maximum size volumes will be created as. + default values: {"min": 1, "max": 5} :param kwargs: Optional parameters used during volume snapshot creation. - """ - if "min_volume_size" in kwargs or "max_volume_size" in kwargs: + if "min_size" in kwargs or "max_size" in kwargs: import warnings - warnings.warn("'min_volume_size' and 'max_volume_size' arguments " - "are deprecated. You should use 'volume_size', with " + warnings.warn("'min_size' and 'max_size' arguments " + "are deprecated. You should use 'size', with " "keys 'min' and 'max' instead.") - if volume_size is None: - volume_size = {"min": 1, "max": 5} + if "volume_size" in kwargs: + import warnings + warnings.warn("'volume_size' argument is deprecated. You should " + "use 'size' instead.") + size = kwargs["volume_size"] + + if size is None: + size = {"min": 1, "max": 5} selected_type = None volume_types = [None] @@ -154,9 +190,9 @@ class CinderVolumes(utils.CinderScenario, volume_types.append(s.name) selected_type = random.choice(volume_types) - volume_size = random.randint(volume_size['min'], volume_size['max']) + size = random.randint(size['min'], size['max']) - volume = self._create_volume(volume_size, volume_type=selected_type) + volume = self._create_volume(size, volume_type=selected_type) snapshot = self._create_snapshot(volume.id, False, **kwargs) server = self.get_random_server() @@ -171,47 +207,53 @@ class CinderVolumes(utils.CinderScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["cinder", "nova"]}) def create_nested_snapshots_and_attach_volume(self, - volume_size=None, + size=None, nested_level=None, **kwargs): - """Tests volume create from snapshot and volume attach/detach + """Create a volume from snapshot and attach/detach the volume This scenario create volume, create it's snapshot, attach volume, then create new volume from existing snapshot and so on, - with defined nested level, after all detach and delete them . + with defined nested level, after all detach and delete them. volume->snapshot->volume->snapshot->volume ... - :param volume_size: Volume size - dictionary, contains two values - min - minimum size volumes will be created as. - max - maximum size volumes will be created as. - default values: {"min": 1, "max": 5} - :param nested_level: Nested level - dictionary, contains two values - min - minimum number of volumes will be create from snapshot - max - maximum number of volumes will be create from snapshot - default values: {"min": 5, "max": 10} + :param size: Volume size - dictionary, contains two values: + min - minimum size volumes will be created as; + max - maximum size volumes will be created as. + default values: {"min": 1, "max": 5} + :param nested_level: Nested level - dictionary, contains two values: + min - minimum number of volumes will be created + from snapshot; + max - maximum number of volumes will be created + from snapshot. + default values: {"min": 5, "max": 10} :param kwargs: Optional parameters used during volume - snapshot creation. - + snapshot creation. """ + if "volume_size" in kwargs: + import warnings + warnings.warn("'volume_size' argument is deprecated. You should " + "use 'size' instead.") + size = kwargs["volume_size"] - if volume_size is None: - volume_size = {"min": 1, "max": 5} + if size is None: + size = {"min": 1, "max": 5} if nested_level is None: nested_level = {"min": 5, "max": 10} - volume_size = random.randint(volume_size['min'], volume_size['max']) + size = random.randint(size['min'], size['max']) nested_level = random.randint(nested_level['min'], nested_level['max']) servers = [self.get_random_server()] - volumes = [self._create_volume(volume_size)] + volumes = [self._create_volume(size)] snapshots = [self._create_snapshot(volumes[0].id, False, **kwargs)] self._attach_volume(servers[0], volumes[0]) snapshot = snapshots[0] for i in range(nested_level - 1): - volume = self._create_volume(volume_size, snapshot_id=snapshot.id) + volume = self._create_volume(size, snapshot_id=snapshot.id) snapshot = self._create_snapshot(volume.id, False, **kwargs) server = self.get_random_server() diff --git a/rally/benchmark/scenarios/designate/basic.py b/rally/benchmark/scenarios/designate/basic.py index bdea3efe22..90628c8412 100644 --- a/rally/benchmark/scenarios/designate/basic.py +++ b/rally/benchmark/scenarios/designate/basic.py @@ -21,15 +21,15 @@ from rally import consts class DesignateBasic(utils.DesignateScenario): + """Basic benchmark scenarios for Designate.""" @validation.required_services(consts.Service.DESIGNATE) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def create_and_list_domains(self): - """Tests creating a domain and listing domains. + """Create a domain and list all domains. - This scenario is a very useful tool to measure - the "designate domain-list" command performance. + Measure the "designate domain-list" command performance. If you have only 1 user in your context, you will add 1 domain on every iteration. So you will have more @@ -44,7 +44,7 @@ class DesignateBasic(utils.DesignateScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def list_domains(self): - """Test the designate domain-list command. + """List Designate domains. This simple scenario tests the designate domain-list command by listing all the domains. @@ -60,10 +60,10 @@ class DesignateBasic(utils.DesignateScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def create_and_delete_domain(self): - """Test adds and then deletes domain. + """Add and then delete a domain. - This is very useful to measure perfromance of creating and deleting - domains with different level of load. + Measure the performance of creating and deleting domains + with different level of load. """ domain = self._create_domain() self._delete_domain(domain['id']) @@ -72,10 +72,10 @@ class DesignateBasic(utils.DesignateScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def create_and_delete_records(self, records_per_domain=5): - """Test adds and then deletes records. + """Add and then delete records. - This is very useful to measure perfromance of creating and deleting - records with different level of load. + Measure the performance of creating and deleting records + with different level of load. :param records_per_domain: Records to create pr domain. """ @@ -99,7 +99,7 @@ class DesignateBasic(utils.DesignateScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def list_records(self, domain_id): - """Test the designate record-list command. + """List Designate records. This simple scenario tests the designate record-list command by listing all the records in a domain. @@ -117,7 +117,7 @@ class DesignateBasic(utils.DesignateScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["designate"]}) def create_and_list_records(self, records_per_domain=5): - """Test adds and then lists records. + """Add and then list records. If you have only 1 user in your context, you will add 1 record on every iteration. So you will have more diff --git a/rally/benchmark/scenarios/designate/utils.py b/rally/benchmark/scenarios/designate/utils.py index 9824349508..0a95251793 100644 --- a/rally/benchmark/scenarios/designate/utils.py +++ b/rally/benchmark/scenarios/designate/utils.py @@ -18,7 +18,7 @@ from rally.benchmark.scenarios import base class DesignateScenario(base.Scenario): - """This class should contain base operations for benchmarking designate.""" + """Base class for Designate scenarios with basic atomic actions.""" RESOURCE_NAME_PREFIX = "rally_" @@ -44,16 +44,18 @@ class DesignateScenario(base.Scenario): def _delete_domain(self, domain_id): """Delete designate zone. - :param domain: Domain object + :param domain_id: domain ID """ self.clients("designate").domains.delete(domain_id) def _create_record(self, domain, record=None, atomic_action=True): """Create a record in a domain. - :param domain: Domain object - :param record: Record object - :returns: designate record dict + :param domain: domain dict + :param record: record dict + :param atomic_action: True if the record creation should be tracked + as an atomic action + :returns: Designate record dict """ record = record or {} record.setdefault('type', 'A') @@ -71,18 +73,20 @@ class DesignateScenario(base.Scenario): @base.atomic_action_timer('designate.list_records') def _list_records(self, domain_id): - """List records in a domain.. + """List domain records. - :param domain_id: Domain ID - :returns: domain record list + :param domain_id: domain ID + :returns: domain records list """ return self.clients("designate").records.list(domain_id) def _delete_record(self, domain_id, record_id, atomic_action=True): - """Delete a record in a domain.. + """Delete a domain record. - :param domain_id: Domain ID - :param record_id: Record ID + :param domain_id: domain ID + :param record_id: record ID + :param atomic_action: True if the record creation should be tracked + as an atomic action """ client = self.clients('designate') diff --git a/rally/benchmark/scenarios/dummy/dummy.py b/rally/benchmark/scenarios/dummy/dummy.py index 3f8bb326e9..b330999d4f 100644 --- a/rally/benchmark/scenarios/dummy/dummy.py +++ b/rally/benchmark/scenarios/dummy/dummy.py @@ -23,17 +23,17 @@ class DummyScenarioException(exceptions.RallyException): class Dummy(base.Scenario): - """Benchmarks for testing Rally benchmark engine at scale.""" + """Dummy benchmarks for testing Rally benchmark engine at scale.""" @base.scenario() def dummy(self, sleep=0): - """Test the performance of ScenarioRunners. + """Do nothing and sleep for the given number of seconds (0 by default). Dummy.dummy can be used for testing performance of different - ScenarioRunners and ability of rally to store a large + ScenarioRunners and of the ability of rally to store a large amount of results. - :param sleep: Idle time of method. + :param sleep: idle time of method (in seconds). """ if sleep: time.sleep(sleep) @@ -42,13 +42,14 @@ class Dummy(base.Scenario): minval=1, integer_only=True, nullable=True) @base.scenario() def dummy_exception(self, size_of_message=1): - """Test if exceptions are processed properly. + """Throw an exception. - Dummy.dummy_exception can be used for test if Exceptions are processed + Dummy.dummy_exception can be used for test if exceptions are processed properly by ScenarioRunners and benchmark and analyze rally results storing process. - :param size_of_message: the size of the message. + :param size_of_message: int size of the exception message + :raises: DummyScenarioException """ raise DummyScenarioException("M" * size_of_message) @@ -57,9 +58,11 @@ class Dummy(base.Scenario): minval=0, maxval=1, integer_only=False, nullable=True) @base.scenario() def dummy_exception_probability(self, exception_probability=0.5): - """Test if exceptions are processed properly. + """Throw an exception with given probability. - This scenario will throw an exception sometimes. + Dummy.dummy_exception_probability can be used to test if exceptions + are processed properly by ScenarioRunners. This scenario will throw + an exception sometimes, depending on the given exception probability. :param exception_probability: Sets how likely it is that an exception will be thrown. Float between 0 and 1 @@ -74,6 +77,11 @@ class Dummy(base.Scenario): @base.scenario() def dummy_with_scenario_output(self): + """Return a dummy scenario output. + + Dummy.dummy_with_scenario_output can be used to test the scenario + output processing. + """ out = { 'value_1': random.randint(1, 100), 'value_2': random.random() @@ -83,10 +91,22 @@ class Dummy(base.Scenario): @base.atomic_action_timer("dummy_fail_test") def _random_fail_emitter(self, exception_probability): + """Throw an exception with given probability. + + :raises: KeyError + """ if random.random() < exception_probability: raise KeyError("Dummy test exception") @base.scenario() def dummy_random_fail_in_atomic(self, exception_probability=0.5): + """Randomly throw exceptions in atomic actions. + + Dummy.dummy_random_fail_in_atomic can be used to test atomic actions + failures processing. + + :param exception_probability: Probability with which atomic actions + fail in this dummy scenario (0 <= p <= 1) + """ self._random_fail_emitter(exception_probability) self._random_fail_emitter(exception_probability) diff --git a/rally/benchmark/scenarios/glance/images.py b/rally/benchmark/scenarios/glance/images.py index 8eeee79310..436b9a4051 100644 --- a/rally/benchmark/scenarios/glance/images.py +++ b/rally/benchmark/scenarios/glance/images.py @@ -22,6 +22,7 @@ from rally import consts class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): + """Benchmark scenarios for Glance images.""" RESOURCE_NAME_PREFIX = "rally_image_" RESOURCE_NAME_LENGTH = 16 @@ -31,16 +32,22 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): @base.scenario(context={"cleanup": ["glance"]}) def create_and_list_image(self, container_format, image_location, disk_format, **kwargs): - """Test adding an image and then listing all images. + """Add an image and then list all images. - This scenario is a very useful tool to measure - the "glance image-list" command performance. + Measure the "glance image-list" command performance. If you have only 1 user in your context, you will add 1 image on every iteration. So you will have more and more images and will be able to measure the performance of the "glance image-list" command depending on the number of images owned by users. + + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param image_location: image file location + :param disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso + :param kwargs: optional parameters to create image """ self._create_image(self._generate_random_name(), container_format, @@ -53,7 +60,7 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["glance"]}) def list_images(self): - """Test the glance image-list command. + """List all images. This simple scenario tests the glance image-list command by listing all the images. @@ -70,7 +77,15 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): @base.scenario(context={"cleanup": ["glance"]}) def create_and_delete_image(self, container_format, image_location, disk_format, **kwargs): - """Test adds and then deletes image.""" + """Add and then delete an image. + + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param image_location: image file location + :param disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso + :param kwargs: optional parameters to create image + """ image_name = self._generate_random_name() image = self._create_image(image_name, container_format, @@ -88,7 +103,17 @@ class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario): image_location, disk_format, flavor, number_instances, **kwargs): - """Test adds image, boots instance from it and then deletes them.""" + """Add an image and boot several instances from it. + + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param image_location: image file location + :param disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso + :param flavor: Nova flavor to be used to launch an instance + :param number_instances: number of Nova servers to boot + :param kwargs: optional parameters to create image / server + """ image_name = self._generate_random_name() image = self._create_image(image_name, container_format, diff --git a/rally/benchmark/scenarios/glance/utils.py b/rally/benchmark/scenarios/glance/utils.py index 9d759f956d..e1810f5804 100644 --- a/rally/benchmark/scenarios/glance/utils.py +++ b/rally/benchmark/scenarios/glance/utils.py @@ -50,11 +50,11 @@ CONF.register_opts(glance_benchmark_opts, group=benchmark_group) class GlanceScenario(base.Scenario): + """Base class for Glance scenarios with basic atomic actions.""" @base.atomic_action_timer('glance.list_images') def _list_images(self): """Returns user images list.""" - return list(self.clients("glance").images.list()) @base.atomic_action_timer('glance.create_image') @@ -62,17 +62,16 @@ class GlanceScenario(base.Scenario): image_location, disk_format, **kwargs): """Create a new image. - :param image_name: String used to name the image - :param container_format: Container format of image. - Acceptable formats: ami, ari, aki, bare, and ovf. - :param image_location: image file location used to upload - :param disk_format: Disk format of image. Acceptable formats: - ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso. - :param **kwargs: optional parameters to create image + :param image_name: string used to name the image + :param container_format: container format of image. Acceptable + formats: ami, ari, aki, bare, and ovf + :param image_location: image file location + :param disk_format: disk format of image. Acceptable formats: + ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso + :param kwargs: optional parameters to create image - returns: object of image + :returns: image object """ - kw = { "name": image_name, "container_format": container_format, @@ -107,7 +106,7 @@ class GlanceScenario(base.Scenario): @base.atomic_action_timer('glance.delete_image') def _delete_image(self, image): - """Deletes the given image. + """Deletes given image. Returns when the image is actually deleted. diff --git a/rally/benchmark/scenarios/heat/stacks.py b/rally/benchmark/scenarios/heat/stacks.py index 0fef698d1d..880f14d857 100644 --- a/rally/benchmark/scenarios/heat/stacks.py +++ b/rally/benchmark/scenarios/heat/stacks.py @@ -20,6 +20,7 @@ from rally import consts class HeatStacks(utils.HeatScenario): + """Benchmark scenarios for Heat stacks.""" RESOURCE_NAME_PREFIX = "rally_stack_" RESOURCE_NAME_LENGTH = 7 @@ -39,13 +40,13 @@ class HeatStacks(utils.HeatScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["heat"]}) def create_and_list_stack(self, template_path=None): - """Test adding an stack and then listing all stacks. + """Add a stack and then list all stacks. - This scenario is a very useful tool to measure - the "heat stack-create" and "heat stack-list" command performance. + Mesure the "heat stack-create" and "heat stack-list" commands + performance. - :param template_path: path to template file. if it's None or incorrect, - will be used default empty template. + :param template_path: path to template file. If None or incorrect, + then default empty template will be used. """ stack_name = self._generate_random_name() @@ -58,13 +59,13 @@ class HeatStacks(utils.HeatScenario): @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["heat"]}) def create_and_delete_stack(self, template_path=None): - """Test adds and then deletes stack. + """Add and then delete a stack. - This scenario is a very useful tool to measure - the "heat stack-create" and "heat stack-delete" command performance. + Measure the "heat stack-create" and "heat stack-delete" commands + performance. - :param template_path: path to template file. if it's None or incorrect, - will be used default empty template. + :param template_path: path to template file. If None or incorrect, + then default empty template will be used. """ stack_name = self._generate_random_name() template = self._get_template_from_file(template_path) diff --git a/rally/benchmark/scenarios/heat/utils.py b/rally/benchmark/scenarios/heat/utils.py index a9e8d901bf..83d2ffe3c2 100644 --- a/rally/benchmark/scenarios/heat/utils.py +++ b/rally/benchmark/scenarios/heat/utils.py @@ -55,6 +55,7 @@ def heat_resource_is(status): class HeatScenario(base.Scenario): + """Base class for Heat scenarios with basic atomic actions.""" default_template = "HeatTemplateFormatVersion: '2012-12-12'" @@ -71,7 +72,7 @@ class HeatScenario(base.Scenario): :param stack_name: string. Name for created stack. :param template: optional parameter. Template with stack description. - returns: object of stack + :returns: object of stack """ template = template or self.default_template @@ -102,7 +103,7 @@ class HeatScenario(base.Scenario): @base.atomic_action_timer('heat.delete_stack') def _delete_stack(self, stack): - """Delete the given stack. + """Delete given stack. Returns when the stack is actually deleted. diff --git a/rally/benchmark/scenarios/keystone/basic.py b/rally/benchmark/scenarios/keystone/basic.py index 30e696c999..228bb927b3 100644 --- a/rally/benchmark/scenarios/keystone/basic.py +++ b/rally/benchmark/scenarios/keystone/basic.py @@ -19,17 +19,30 @@ from rally.benchmark import validation class KeystoneBasic(kutils.KeystoneScenario): + """Basic benchmark scenarios for Keystone.""" @validation.number("name_length", minval=10) @validation.required_openstack(admin=True) @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_user(self, name_length=10, **kwargs): + """Create a keystone user with random name. + + :param name_length: length of the random part of user name + :param kwargs: Other optional parameters to create users like + "tenant_id", "enabled". + """ self._user_create(name_length=name_length, **kwargs) @validation.number("name_length", minval=10) @validation.required_openstack(admin=True) @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_delete_user(self, name_length=10, **kwargs): + """Create a keystone user with random name and then delete it. + + :param name_length: length of the random part of user name + :param kwargs: Other optional parameters to create users like + "tenant_id", "enabled". + """ user = self._user_create(name_length=name_length, **kwargs) self._resource_delete(user) @@ -37,6 +50,11 @@ class KeystoneBasic(kutils.KeystoneScenario): @validation.required_openstack(admin=True) @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_tenant(self, name_length=10, **kwargs): + """Create a keystone tenant with random name. + + :param name_length: length of the random part of tenant name + :param kwargs: Other optional parameters + """ self._tenant_create(name_length=name_length, **kwargs) @validation.number("name_length", minval=10) @@ -45,6 +63,13 @@ class KeystoneBasic(kutils.KeystoneScenario): @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_tenant_with_users(self, users_per_tenant, name_length=10, **kwargs): + """Create a keystone tenant and several users belonging to it. + + :param name_length: length of the random part of tenant/user name + :param users_per_tenant: number of users to create for the tenant + :param kwargs: Other optional parameters for tenant creation + :returns: keystone tenant instance + """ tenant = self._tenant_create(name_length=name_length, **kwargs) self._users_create(tenant, users_per_tenant=users_per_tenant, name_length=name_length) @@ -53,6 +78,12 @@ class KeystoneBasic(kutils.KeystoneScenario): @validation.required_openstack(admin=True) @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_and_list_users(self, name_length=10, **kwargs): + """Create a keystone user with random name and list all users. + + :param name_length: length of the random part of user name + :param kwargs: Other optional parameters to create users like + "tenant_id", "enabled". + """ self._user_create(name_length=name_length, **kwargs) self._list_users() @@ -60,5 +91,10 @@ class KeystoneBasic(kutils.KeystoneScenario): @validation.required_openstack(admin=True) @base.scenario(context={"admin_cleanup": ["keystone"]}) def create_and_list_tenants(self, name_length=10, **kwargs): + """Create a keystone tenant with random name and list all tenants. + + :param name_length: length of the random part of tenant name + :param kwargs: Other optional parameters + """ self._tenant_create(name_length=name_length, **kwargs) self._list_tenants() diff --git a/rally/benchmark/scenarios/keystone/utils.py b/rally/benchmark/scenarios/keystone/utils.py index 0f6c30b3ec..bded64ad8e 100644 --- a/rally/benchmark/scenarios/keystone/utils.py +++ b/rally/benchmark/scenarios/keystone/utils.py @@ -23,6 +23,8 @@ def is_temporary(resource): class KeystoneScenario(base.Scenario): + """Base class for Keystone scenarios with basic atomic actions.""" + RESOURCE_NAME_PREFIX = "rally_keystone_" @base.atomic_action_timer('keystone.create_user') @@ -30,9 +32,9 @@ class KeystoneScenario(base.Scenario): """Creates keystone user with random name. :param name_length: length of generated (random) part of name - :param **kwargs: Other optional parameters to create users like + :param kwargs: Other optional parameters to create users like "tenant_id", "enabled". - :return: keystone user instance + :returns: keystone user instance """ name = self._generate_random_name(length=name_length) # NOTE(boris-42): password and email parameters are required by @@ -54,8 +56,8 @@ class KeystoneScenario(base.Scenario): """Creates keystone tenant with random name. :param name_length: length of generated (random) part of name - :param **kwargs: Other optional parameters - :return: keystone tenant instance + :param kwargs: Other optional parameters + :returns: keystone tenant instance """ name = self._generate_random_name(length=name_length) return self.admin_clients("keystone").tenants.create(name, **kwargs) @@ -64,6 +66,7 @@ class KeystoneScenario(base.Scenario): def _users_create(self, tenant, users_per_tenant, name_length=10): """Adds users to a tenant. + :param tenant: tenant object :param users_per_tenant: number of users in per tenant :param name_length: length of generated (random) part of name for user """ @@ -76,10 +79,10 @@ class KeystoneScenario(base.Scenario): @base.atomic_action_timer('keystone.list_users') def _list_users(self): - """list users.""" + """List users.""" return self.admin_clients("keystone").users.list() @base.atomic_action_timer('keystone.list_tenants') def _list_tenants(self): - """list tenants.""" + """List tenants.""" return self.admin_clients("keystone").tenants.list() diff --git a/rally/benchmark/scenarios/neutron/network.py b/rally/benchmark/scenarios/neutron/network.py index 6041c67a5d..8a50f6b3ff 100644 --- a/rally/benchmark/scenarios/neutron/network.py +++ b/rally/benchmark/scenarios/neutron/network.py @@ -20,15 +20,15 @@ from rally import consts class NeutronNetworks(utils.NeutronScenario): + """Benchmark scenarios for Neutron.""" @validation.required_services(consts.Service.NEUTRON) @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["neutron"]}) def create_and_list_networks(self, network_create_args=None): - """Create a network and then listing all networks. + """Create a network and then list all networks. - This scenario is a very useful tool to measure - the "neutron net-list" command performance. + Measure the "neutron net-list" command performance. If you have only 1 user in your context, you will add 1 network on every iteration. So you will have more @@ -47,10 +47,9 @@ class NeutronNetworks(utils.NeutronScenario): def create_and_update_networks(self, network_update_args, network_create_args=None): - """Create a network and then update network. + """Create and update a network. - This scenario is a very useful tool to measure - the "neutron net-create and net-update" command performance. + Measure the "neutron net-create and net-update" command performance. :param network_update_args: dict, PUT /v2.0/networks update request :param network_create_args: dict, POST /v2.0/networks request options @@ -61,12 +60,11 @@ class NeutronNetworks(utils.NeutronScenario): @validation.required_services(consts.Service.NEUTRON) @base.scenario(context={"cleanup": ["neutron"]}) def create_and_delete_networks(self, network_create_args=None): - """Create a network and then deleting it. + """Create and delete a network. - This scenario is a very useful tool to measure - the "neutron net-create" and "net-delete" command performance. + Measure the "neutron net-create" and "net-delete" command performance. - :param network_create_agrs: dict, POST /v2.0/networks request options + :param network_create_args: dict, POST /v2.0/networks request options """ network = self._create_network(network_create_args or {}) self._delete_network(network['network']) @@ -80,7 +78,7 @@ class NeutronNetworks(utils.NeutronScenario): subnet_create_args=None, subnet_cidr_start=None, subnets_per_network=None): - """Test creating and listing a given number of subnets. + """Create and a given number of subnets and list all subnets. The scenario creates a network, a given number of subnets and then lists subnets. @@ -106,10 +104,10 @@ class NeutronNetworks(utils.NeutronScenario): subnet_create_args=None, subnet_cidr_start=None, subnets_per_network=None): - """Create a subnet and then update subnet. + """Create and update a subnet. The scenario creates a network, a given number of subnets - and then updates the subnet. This scenario measure the + and then updates the subnet. This scenario measures the "neutron subnet-update" command performance. :param subnet_update_args: dict, PUT /v2.0/subnets update options @@ -135,7 +133,7 @@ class NeutronNetworks(utils.NeutronScenario): subnet_create_args=None, subnet_cidr_start=None, subnets_per_network=None): - """Test creating and deleting a given number of subnets. + """Create and delete a given number of subnets. The scenario creates a network, a given number of subnets and then deletes subnets. @@ -164,7 +162,7 @@ class NeutronNetworks(utils.NeutronScenario): subnet_cidr_start=None, subnets_per_network=None, router_create_args=None): - """Test creating and listing a given number of routers. + """Create and a given number of routers and list all routers. Create a network, a given number of subnets and routers and then list all routers. @@ -200,7 +198,7 @@ class NeutronNetworks(utils.NeutronScenario): subnet_cidr_start=None, subnets_per_network=None, router_create_args=None): - """Test creating and updating a given number of routers. + """Create and update a given number of routers. Create a network, a given number of subnets and routers and then updating all routers. @@ -233,7 +231,7 @@ class NeutronNetworks(utils.NeutronScenario): network_create_args=None, port_create_args=None, ports_per_network=None): - """Test creating and listing a given number of ports. + """Create and a given number of ports and list all ports. :param network_create_args: dict, POST /v2.0/networks request options :param port_create_args: dict, POST /v2.0/ports request options @@ -254,11 +252,10 @@ class NeutronNetworks(utils.NeutronScenario): network_create_args=None, port_create_args=None, ports_per_network=None): - """Test creating and updating a given number of ports. + """Create and update a given number of ports. - This scenario is a very useful tool to measure - the "neutron port-create" and - "neutron port-update" command performance. + Measure the "neutron port-create" and "neutron port-update" commands + performance. :param port_update_args: dict, PUT /v2.0/ports update request options :param network_create_args: dict, POST /v2.0/networks request options @@ -277,11 +274,10 @@ class NeutronNetworks(utils.NeutronScenario): network_create_args=None, port_create_args=None, ports_per_network=None): - """Create a port and then deleting it. + """Create and delete a port. - This scenario is a very useful tool to measure - the "neutron port-create" and - "neutron port-delete" command performance. + Measure the "neutron port-create" and "neutron port-delete" commands + performance. :param network_create_args: dict, POST /v2.0/networks request options :param port_create_args: dict, POST /v2.0/ports request options diff --git a/rally/benchmark/scenarios/neutron/utils.py b/rally/benchmark/scenarios/neutron/utils.py index 991b7dc9fd..247de2b7e0 100644 --- a/rally/benchmark/scenarios/neutron/utils.py +++ b/rally/benchmark/scenarios/neutron/utils.py @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) class NeutronScenario(base.Scenario): - """This class should contain base operations for benchmarking neutron.""" + """Base class for Neutron scenarios with basic atomic actions.""" RESOURCE_NAME_PREFIX = "rally_net_" RESOURCE_NAME_LENGTH = 16 diff --git a/rally/benchmark/scenarios/nova/security_group.py b/rally/benchmark/scenarios/nova/security_group.py index 44d72e744c..f7150acd26 100644 --- a/rally/benchmark/scenarios/nova/security_group.py +++ b/rally/benchmark/scenarios/nova/security_group.py @@ -27,6 +27,7 @@ class NovaSecurityGroupException(exceptions.RallyException): class NovaSecGroup(utils.NovaScenario): + """Benchmark scenarios for Nova security groups.""" RESOURCE_NAME_PREFIX = "rally_novasecgrp_" @@ -37,7 +38,7 @@ class NovaSecGroup(utils.NovaScenario): @base.scenario(context={"cleanup": ["nova"]}) def create_and_delete_secgroups(self, security_group_count, rules_per_security_group): - """Tests creating and deleting security groups. + """Create and delete security groups. This scenario creates N security groups with M rules per group and then deletes them. @@ -61,7 +62,7 @@ class NovaSecGroup(utils.NovaScenario): @base.scenario(context={"cleanup": ["nova"]}) def create_and_list_secgroups(self, security_group_count, rules_per_security_group): - """Tests creating and listing security groups. + """Create and list security groups. This scenario creates N security groups with M rules per group and then lists them. @@ -89,7 +90,7 @@ class NovaSecGroup(utils.NovaScenario): def boot_and_delete_server_with_secgroups(self, image, flavor, security_group_count, rules_per_security_group): - """Tests booting an image with security groups attached. + """Boot and delete server with security groups attached. Plan of this scenario: - create N security groups with M rules per group diff --git a/rally/benchmark/scenarios/nova/servers.py b/rally/benchmark/scenarios/nova/servers.py index dc6ad9e5b3..60cf214641 100644 --- a/rally/benchmark/scenarios/nova/servers.py +++ b/rally/benchmark/scenarios/nova/servers.py @@ -31,6 +31,7 @@ LOG = logging.getLogger(__name__) class NovaServers(utils.NovaScenario, cinder_utils.CinderScenario): + """Benchmark scenarios for Nova servers.""" RESOURCE_NAME_PREFIX = "rally_novaserver_" RESOURCE_NAME_LENGTH = 16 @@ -43,16 +44,21 @@ class NovaServers(utils.NovaScenario, @base.scenario(context={"cleanup": ["nova"]}) def boot_and_list_server(self, image, flavor, detailed=True, **kwargs): - """Tests booting an image and then listing servers. + """Boot a server from an image and then list all servers. - This scenario is a very useful tool to measure - the "nova list" command performance. + Measure the "nova list" command performance. - If you have only 1 user in your context, you will - add 1 server on every iteration. So you will have more - and more servers and will be able to measure the - performance of the "nova list" command depending on - the number of servers owned by users. + If you have only 1 user in your context, you will + add 1 server on every iteration. So you will have more + and more servers and will be able to measure the + performance of the "nova list" command depending on + the number of servers owned by users. + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param detailed: True if the server listing should contain + detailed information about all of them + :param kwargs: Optional additional arguments for server creation """ self._boot_server( self._generate_random_name(), image, flavor, **kwargs) @@ -62,7 +68,7 @@ class NovaServers(utils.NovaScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["nova"]}) def list_servers(self, detailed=True): - """Test the nova list command. + """List all servers. This simple scenario test the nova list command by listing all the servers. @@ -81,7 +87,19 @@ class NovaServers(utils.NovaScenario, def boot_and_delete_server(self, image, flavor, min_sleep=0, max_sleep=0, force_delete=False, **kwargs): - """Tests booting and then deleting an image.""" + """Boot and delete a server. + + Optional 'min_sleep' and 'max_sleep' parameters allow the scenario + to simulate a pause between volume creation and deletion + (of random duration from [min_sleep, max_sleep]). + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param min_sleep: Minimum sleep time in seconds (non-negative) + :param max_sleep: Maximum sleep time in seconds (non-negative) + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for server creation + """ server = self._boot_server( self._generate_random_name(), image, flavor, **kwargs) self.sleep_between(min_sleep, max_sleep) @@ -97,7 +115,21 @@ class NovaServers(utils.NovaScenario, volume_size, min_sleep=0, max_sleep=0, force_delete=False, **kwargs): - """Tests booting from volume and then deleting an image and volume.""" + """Boot a server from volume and then delete it. + + The scenario first creates a volume and then a server. + Optional 'min_sleep' and 'max_sleep' parameters allow the scenario + to simulate a pause between volume creation and deletion + (of random duration from [min_sleep, max_sleep]). + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param volume_size: volume size (in GB) + :param min_sleep: Minimum sleep time in seconds (non-negative) + :param max_sleep: Maximum sleep time in seconds (non-negative) + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for server creation + """ volume = self._create_volume(volume_size, imageRef=image) block_device_mapping = {'vda': '%s:::1' % volume.id} server = self._boot_server(self._generate_random_name(), @@ -114,15 +146,24 @@ class NovaServers(utils.NovaScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["nova"]}) def boot_and_bounce_server(self, image, flavor, - force_delete=False, **kwargs): - """Test booting a server with further performing specified actions. + force_delete=False, actions=None, **kwargs): + """Boot a server and run specified actions against it. - Actions should be passed into kwargs. Available actions are - 'hard_reboot', 'soft_reboot', 'stop_start' and 'rescue_unrescue'. - Delete server after all actions. + Actions should be passed into the actions parameter. Available actions + are 'hard_reboot', 'soft_reboot', 'stop_start' and 'rescue_unrescue'. + Delete server after all actions were completed. + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param force_delete: True if force_delete should be used + :param actions: list of action dictionaries, where each action + dictionary speicifes an action to be performed + in the following format: + {"action_name": <no_of_iterations>} + :param kwargs: Optional additional arguments for server creation """ action_builder = self._bind_actions() - actions = kwargs.get('actions', []) + actions = actions or [] try: action_builder.validate(actions) except jsonschema.exceptions.ValidationError as error: @@ -143,7 +184,13 @@ class NovaServers(utils.NovaScenario, @base.scenario(context={"cleanup": ["nova", "glance"]}) def snapshot_server(self, image, flavor, force_delete=False, **kwargs): - """Tests Nova instance snapshotting.""" + """Boot a server, make its snapshot and delete both. + + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for server creation + """ server_name = self._generate_random_name() server = self._boot_server(server_name, image, flavor, **kwargs) @@ -161,7 +208,15 @@ class NovaServers(utils.NovaScenario, @validation.required_openstack(users=True) @base.scenario(context={"cleanup": ["nova"]}) def boot_server(self, image, flavor, auto_assign_nic=False, **kwargs): - """Test VM boot - assumed clean-up is done elsewhere.""" + """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 auto_assign_nic: True if NICs should be assigned + :param kwargs: Optional additional arguments for server creation + """ server_name = self._generate_random_name() self._boot_server(server_name, image, flavor, auto_assign_nic, **kwargs) @@ -174,7 +229,17 @@ class NovaServers(utils.NovaScenario, @base.scenario(context={"cleanup": ["nova", "cinder"]}) def boot_server_from_volume(self, image, flavor, volume_size, auto_assign_nic=False, **kwargs): - """Test VM boot from volume - assumed clean-up is done elsewhere.""" + """Boot a server from volume. + + The scenario first creates a volume and then 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 volume_size: volume size (in GB) + :param auto_assign_nic: True if NICs should be assigned + :param kwargs: Optional additional arguments for server creation + """ volume = self._create_volume(volume_size, imageRef=image) block_device_mapping = {'vda': '%s:::1' % volume.id} self._boot_server(self._generate_random_name(), @@ -232,7 +297,17 @@ class NovaServers(utils.NovaScenario, @base.scenario(context={"cleanup": ["nova"]}) def resize_server(self, image, flavor, to_flavor, force_delete=False, **kwargs): - """Tests resize serveri.""" + """Boot a server, then resize and delete it. + + The scenario first creates a volume and then 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 to_flavor: flavor to be used to resize the booted instance + :param force_delete: True if force_delete should be used + :param kwargs: Optional additional arguments for server creation + """ server = self._boot_server(self._generate_random_name(), image, flavor, **kwargs) self._resize(server, to_flavor) @@ -253,17 +328,18 @@ class NovaServers(utils.NovaScenario, def boot_and_live_migrate_server(self, image, flavor, block_migration=False, disk_over_commit=False, **kwargs): - """Tests VM Live Migration. + """Live Migrate a server. This scenario launches a VM on a compute node available in the availability zone and then migrates the VM to another compute node on the same availability zone. - :param image: Glance image to be used to launch an instance - :param flavor: Nova flavor to be used to launch an instance + :param image: image to be used to boot an instance + :param flavor: flavor to be used to boot an instance :param block_migration: Specifies the migration type :param disk_over_commit: Specifies whether to allow overcommit on migrated instance or not + :param kwargs: Optional additional arguments for server creation """ server = self._boot_server(self._generate_random_name(), image, flavor, **kwargs) diff --git a/rally/benchmark/scenarios/nova/utils.py b/rally/benchmark/scenarios/nova/utils.py index a053748bd8..6d8b1a6a66 100644 --- a/rally/benchmark/scenarios/nova/utils.py +++ b/rally/benchmark/scenarios/nova/utils.py @@ -70,17 +70,17 @@ CONF.register_opts(nova_benchmark_opts, group=benchmark_group) class NovaScenario(base.Scenario): + """Base class for Nova scenarios with basic atomic actions.""" @base.atomic_action_timer('nova.list_servers') def _list_servers(self, detailed=True): """Returns user servers list.""" - return self.clients("nova").servers.list(detailed) @base.atomic_action_timer("nova.boot_server") def _boot_server(self, server_name, image_id, flavor_id, auto_assign_nic=False, **kwargs): - """Boots a server. + """Boot a server. Returns when the server is actually booted and in "ACTIVE" state. @@ -91,7 +91,7 @@ class NovaScenario(base.Scenario): :param image_id: int, image ID for server creation :param flavor_id: int, flavor ID for server creation :param auto_assign_nic: bool, whether or not to auto assign NICs - :param **kwargs: other optional parameters to initialize the server + :param kwargs: other optional parameters to initialize the server :returns: nova Server instance """ allow_ssh_secgroup = self.context.get("allow_ssh") @@ -139,7 +139,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.soft_reboot_server') def _soft_reboot_server(self, server): - """Reboots the given server using soft reboot. + """Reboot a server with soft reboot. A soft reboot will be issued on the given server upon which time this method will wait for the server to become active. @@ -150,7 +150,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.reboot_server') def _reboot_server(self, server): - """Reboots the given server using hard reboot. + """Reboot a server with hard reboot. A reboot will be issued on the given server upon which time this method will wait for the server to become active. @@ -161,7 +161,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.start_server') def _start_server(self, server): - """Starts the given server. + """Start the given server. A start will be issued for the given server upon which time this method will wait for it to become ACTIVE. @@ -247,7 +247,7 @@ class NovaScenario(base.Scenario): ) def _delete_server(self, server, force=False): - """Deletes the given server. + """Delete the given server. Returns when the server is actually deleted. @@ -269,7 +269,7 @@ class NovaScenario(base.Scenario): ) def _delete_all_servers(self, force=False): - """Deletes all servers in current tenant. + """Delete all servers in the current tenant. :param force: If True, force_delete will be used instead of delete. """ @@ -282,7 +282,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.delete_image') def _delete_image(self, image): - """Deletes the given image. + """Delete the given image. Returns when the image is actually deleted. @@ -299,7 +299,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.create_image') def _create_image(self, server): - """Creates an image of the given server + """Create an image from the given server Uses the server name to name the created image. Returns when the image is actually created and is in the "Active" state. @@ -324,7 +324,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.boot_servers') def _boot_servers(self, name_prefix, image_id, flavor_id, requests, instances_amount=1, **kwargs): - """Boots multiple servers. + """Boot multiple servers. Returns when all the servers are actually booted and are in the "Active" state. @@ -362,12 +362,12 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.list_floating_ip_pools') def _list_floating_ip_pools(self): - """Returns user floating ip pools list.""" + """Return user floating ip pools list.""" return self.clients("nova").floating_ip_pools.list() @base.atomic_action_timer('nova.list_floating_ips') def _list_floating_ips(self): - """Returns user floating ips list.""" + """Return user floating ips list.""" return self.clients("nova").floating_ips.list() @base.atomic_action_timer('nova.create_floating_ip') @@ -440,7 +440,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.list_networks') def _list_networks(self): - """Returns user networks list.""" + """Return user networks list.""" return self.clients("nova").networks.list() @base.atomic_action_timer('nova.resize') @@ -512,12 +512,12 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.live_migrate') def _live_migrate(self, server, target_host, block_migration=False, disk_over_commit=False, skip_host_check=False): - """Live Migration of an specified server(Instance). + """Run live migration of the given server. :param server: Server object :param target_host: Specifies the target compute node to migrate :param block_migration: Specifies the migration type - :Param disk_over_commit: Specifies whether to overcommit migrated + :param disk_over_commit: Specifies whether to overcommit migrated instance or not :param skip_host_check: Specifies whether to verify the targeted host availability @@ -544,7 +544,7 @@ class NovaScenario(base.Scenario): @base.atomic_action_timer('nova.find_host_to_migrate') def _find_host_to_migrate(self, server): - """Finds a compute node for live migration. + """Find a compute node for live migration. :param server: Server object """ @@ -600,7 +600,6 @@ class NovaScenario(base.Scenario): self.clients("nova").security_groups.delete(sg.id) def _list_security_groups(self): - """Returns security groups list.""" - + """Return security groups list.""" with base.AtomicAction(self, "nova.list_security_groups"): return self.clients("nova").security_groups.list() diff --git a/rally/benchmark/scenarios/quotas/quotas.py b/rally/benchmark/scenarios/quotas/quotas.py index 4d1827ca29..bf58a24edc 100644 --- a/rally/benchmark/scenarios/quotas/quotas.py +++ b/rally/benchmark/scenarios/quotas/quotas.py @@ -20,12 +20,13 @@ from rally import consts class Quotas(utils.QuotasScenario): + """Benchmark scenarios for quotas.""" @validation.required_services(consts.Service.NOVA) @validation.required_openstack(admin=True, users=True) @base.scenario(context={"admin_cleanup": ["nova.quotas"]}) def nova_update(self, max_quota=1024): - """Tests updating quotas for nova. + """Update quotas for Nova. :param max_quota: Max value to be updated for quota. """ @@ -36,7 +37,7 @@ class Quotas(utils.QuotasScenario): @validation.required_openstack(admin=True, users=True) @base.scenario(context={"admin_cleanup": ["nova.quotas"]}) def nova_update_and_delete(self, max_quota=1024): - """Tests updating and deleting quotas for nova. + """Update and delete quotas for Nova. :param max_quota: Max value to be updated for quota. """ @@ -49,7 +50,7 @@ class Quotas(utils.QuotasScenario): @validation.required_openstack(admin=True, users=True) @base.scenario(context={"admin_cleanup": ["cinder.quotas"]}) def cinder_update(self, max_quota=1024): - """Tests updating quotas for cinder. + """Update quotas for Cinder. :param max_quota: Max value to be updated for quota. """ @@ -60,7 +61,7 @@ class Quotas(utils.QuotasScenario): @validation.required_openstack(admin=True, users=True) @base.scenario(context={"admin_cleanup": ["cinder.quotas"]}) def cinder_update_and_delete(self, max_quota=1024): - """Tests updating and deleting quotas for cinder. + """Update and Delete quotas for Cinder. :param max_quota: Max value to be updated for quota. """ diff --git a/rally/benchmark/scenarios/quotas/utils.py b/rally/benchmark/scenarios/quotas/utils.py index 13a7032393..371440ccb3 100644 --- a/rally/benchmark/scenarios/quotas/utils.py +++ b/rally/benchmark/scenarios/quotas/utils.py @@ -19,10 +19,11 @@ from rally.benchmark.scenarios import base class QuotasScenario(base.Scenario): + """Base class for quotas scenarios with basic atomic actions.""" @base.atomic_action_timer('quotas.update_quotas') def _update_quotas(self, component, tenant_id, max_quota=1024): - """Updates quotas. + """Update quotas. :param component: Component for the quotas. :param tenant_id: The project_id for the quotas to be updated. @@ -35,7 +36,7 @@ class QuotasScenario(base.Scenario): @base.atomic_action_timer('quotas.delete_quotas') def _delete_quotas(self, component, tenant_id): - """Deletes quotas. + """Delete quotas. :param component: Component for the quotas. :param tenant_id: The project_id for the quotas to be updated. diff --git a/rally/benchmark/scenarios/requests/http_requests.py b/rally/benchmark/scenarios/requests/http_requests.py index 7bd167ca3a..cbbbdc5731 100644 --- a/rally/benchmark/scenarios/requests/http_requests.py +++ b/rally/benchmark/scenarios/requests/http_requests.py @@ -22,7 +22,7 @@ class WrongStatusException(exceptions.RallyException): class Requests(base.Scenario): - """This class should contain all the http_request scenarios.""" + """Benchmark scenarios for HTTP requests.""" @base.scenario() def check_response(self, url, response=None): @@ -32,7 +32,7 @@ class Requests(base.Scenario): Response. :param url: URL to be fetched - :param response: Expected Response Code + :param response: expected response code """ resp = requests.head(url) if response and response != resp.status_code: diff --git a/rally/benchmark/scenarios/sahara/clusters.py b/rally/benchmark/scenarios/sahara/clusters.py index b2d6693021..9642b12c85 100644 --- a/rally/benchmark/scenarios/sahara/clusters.py +++ b/rally/benchmark/scenarios/sahara/clusters.py @@ -24,6 +24,7 @@ LOG = logging.getLogger(__name__) class SaharaClusters(utils.SaharaScenario): + """Benchmark scenarios for Sahara clusters.""" @types.set(flavor=types.FlavorResourceType, neutron_net=types.NeutronNetworkResourceType, @@ -40,37 +41,39 @@ class SaharaClusters(utils.SaharaScenario): volumes_size=None, auto_security_group=None, security_groups=None, node_configs=None, cluster_configs=None): - """Test the Sahara Cluster launch and delete commands. + """Launch and delete a Sahara Cluster. This scenario launches a Hadoop cluster, waits until it becomes 'Active' and deletes it. - :param flavor: The Nova flavor that will be for nodes in the - created node groups - :param node_count: The total number of instances in a cluster (>= 2) - :param plugin_name: The name of a provisioning plugin - :param hadoop_version: The version of Hadoop distribution supported by - the specified plugin. - :param floating_ip_pool: The floating ip pool name from which Floating - IPs will be allocated. Sahara will determine automatically how to treat - this depending on it's own configurations. Defaults to None because in - some cases Sahara may work w/o Floating IPs. - :param neutron_net: The id or name of a Neutron network that - will be used for fixed IPs. This parameter is ignored when Nova Network - is set up. - :param volumes_per_node: The number of Cinder volumes that will be - attached to every cluster node - :param volumes_size: The size of each Cinder volume in GB - :param auto_security_group: Boolean value. If set to True Sahara will - create a Security Group for each Node Group in the Cluster - automatically. - :param security_groups: The list of security groups that will be used - while creating VMs. If auto_security_group is set to True this list - can be left empty. - :param node_configs: The configs dict that will be passed to each Node - Group - :param cluster_configs: The configs dict that will be passed to the - Cluster + :param flavor: Nova flavor that will be for nodes in the + created node groups + :param node_count: total number of instances in a cluster (>= 2) + :param plugin_name: name of a provisioning plugin + :param hadoop_version: version of Hadoop distribution supported by + the specified plugin. + :param floating_ip_pool: floating ip pool name from which Floating + IPs will be allocated. Sahara will determine + automatically how to treat this depending on + it's own configurations. Defaults to None + because in some cases Sahara may work w/o + Floating IPs. + :param neutron_net: id or name of a Neutron network that + will be used for fixed IPs. This parameter is + ignored when Nova Network is set up. + :param volumes_per_node: number of Cinder volumes that will be + attached to every cluster node + :param volumes_size: size of each Cinder volume in GB + :param auto_security_group: boolean value. If set to True Sahara will + create a Security Group for each Node Group + in the Cluster automatically. + :param security_groups: list of security groups that will be used + while creating VMs. If auto_security_group + is set to True, this list can be left empty. + :param node_configs: config dict that will be passed to each Node + Group + :param cluster_configs: config dict that will be passed to the + Cluster """ image_id = self.context["tenant"]["sahara_image"] @@ -107,7 +110,7 @@ class SaharaClusters(utils.SaharaScenario): auto_security_group=None, security_groups=None, node_configs=None, cluster_configs=None): - """Test the Sahara Cluster launch, scale and delete commands. + """Launch, scale and delete a Sahara Cluster. This scenario launches a Hadoop cluster, waits until it becomes 'Active'. Then a series of scale operations is applied. The scaling @@ -115,34 +118,36 @@ class SaharaClusters(utils.SaharaScenario): deltas is set to [2, -2] it means that the first scaling operation will add 2 worker nodes to the cluster and the second will remove two. - :param flavor: The Nova flavor that will be for nodes in the - created node groups - :param node_count: The total number of instances in a cluster (>= 2) - :param plugin_name: The name of a provisioning plugin - :param hadoop_version: The version of Hadoop distribution supported by - the specified plugin. - :param deltas: The list of integers which will be used to add or - remove worker nodes from the cluster - :param floating_ip_pool: The floating ip pool name from which Floating - IPs will be allocated. Sahara will determine automatically how to treat - this depending on it's own configurations. Defaults to None because in - some cases Sahara may work w/o Floating IPs. - :param neutron_net_id: The id of a Neutron network that - will be used for fixed IPs. This parameter is ignored when Nova Network - is set up. - :param volumes_per_node: The number of Cinder volumes that will be - attached to every cluster node - :param volumes_size: The size of each Cinder volume in GB - :param auto_security_group: Boolean value. If set to True Sahara will - create a Security Group for each Node Group in the Cluster - automatically. - :param security_groups: The list of security groups that will be used - while creating VMs. If auto_security_group is set to True this list - can be left empty. - :param node_configs: The configs dict that will be passed to each Node - Group - :param cluster_configs: The configs dict that will be passed to the - Cluster + :param flavor: Nova flavor that will be for nodes in the + created node groups + :param node_count: total number of instances in a cluster (>= 2) + :param plugin_name: name of a provisioning plugin + :param hadoop_version: version of Hadoop distribution supported by + the specified plugin. + :param deltas: list of integers which will be used to add or + remove worker nodes from the cluster + :param floating_ip_pool: floating ip pool name from which Floating + IPs will be allocated. Sahara will determine + automatically how to treat this depending on + it's own configurations. Defaults to None + because in some cases Sahara may work w/o + Floating IPs. + :param neutron_net_id: id of a Neutron network that will be used + for fixed IPs. This parameter is ignored when + Nova Network is set up. + :param volumes_per_node: number of Cinder volumes that will be + attached to every cluster node + :param volumes_size: size of each Cinder volume in GB + :param auto_security_group: boolean value. If set to True Sahara will + create a Security Group for each Node Group + in the Cluster automatically. + :param security_groups: list of security groups that will be used + while creating VMs. If auto_security_group + is set to True this list can be left empty. + :param node_configs: configs dict that will be passed to each Node + Group + :param cluster_configs: configs dict that will be passed to the + Cluster """ image_id = self.context["tenant"]["sahara_image"] diff --git a/rally/benchmark/scenarios/sahara/jobs.py b/rally/benchmark/scenarios/sahara/jobs.py index cca5b85f66..edabd6b42f 100644 --- a/rally/benchmark/scenarios/sahara/jobs.py +++ b/rally/benchmark/scenarios/sahara/jobs.py @@ -23,22 +23,23 @@ LOG = logging.getLogger(__name__) class SaharaJob(utils.SaharaScenario): + """Benchmark scenarios for Sahara jobs.""" @validation.required_services(consts.Service.SAHARA) @validation.required_contexts("users", "sahara_image", "sahara_edp", "sahara_cluster") @base.scenario(context={"cleanup": ["sahara"]}) def create_launch_job(self, job_type, configs, job_idx=0): - """Test the Sahara EDP Job execution. - - :param job_type: The type of the Data Processing Job - :param configs: The configs dict that will be passed to a Job Execution - :param job_idx: The index of a job in a sequence. This index will be - used to create different atomic actions for each job in a sequence + """Create and execute a Sahara EDP Job. This scenario Creates a Job entity and launches an execution on a Cluster. + :param job_type: type of the Data Processing Job + :param configs: config dict that will be passed to a Job Execution + :param job_idx: index of a job in a sequence. This index will be + used to create different atomic actions for each job + in a sequence """ mains = self.context["tenant"]["sahara_mains"] @@ -72,13 +73,12 @@ class SaharaJob(utils.SaharaScenario): "sahara_cluster") @base.scenario(context={"cleanup": ["sahara"]}) def create_launch_job_sequence(self, jobs): - """Test the Sahara EDP Job sequence execution. - - :param jobs: The list of jobs that should be executed in one context + """Create and execute a sequence of the Sahara EDP Jobs. This scenario Creates a Job entity and launches an execution on a Cluster for every job object provided. + :param jobs: list of jobs that should be executed in one context """ for idx, job in enumerate(jobs): @@ -90,16 +90,15 @@ class SaharaJob(utils.SaharaScenario): "sahara_cluster") @base.scenario(context={"cleanup": ["sahara"]}) def create_launch_job_sequence_with_scaling(self, jobs, deltas): - """Test the Sahara EDP Job sequence execution on a scaling Cluster. - - :param jobs: The list of jobs that should be executed in one context - :param deltas: The list of integers which will be used to add or - remove worker nodes from the cluster + """Create and execute Sahara EDP Jobs on a scaling Cluster. This scenario Creates a Job entity and launches an execution on a Cluster for every job object provided. The Cluster is scaled according - to the deltas values and the sequence is launched again + to the deltas values and the sequence is launched again. + :param jobs: list of jobs that should be executed in one context + :param deltas: list of integers which will be used to add or + remove worker nodes from the cluster """ cluster_id = self.context["tenant"]["sahara_cluster"] diff --git a/rally/benchmark/scenarios/sahara/node_group_templates.py b/rally/benchmark/scenarios/sahara/node_group_templates.py index cae2b841e0..07198e7872 100644 --- a/rally/benchmark/scenarios/sahara/node_group_templates.py +++ b/rally/benchmark/scenarios/sahara/node_group_templates.py @@ -21,6 +21,7 @@ from rally import consts class SaharaNodeGroupTemplates(utils.SaharaScenario): + """Benchmark scenarios for Sahara node group templates.""" @types.set(flavor=types.FlavorResourceType) @validation.flavor_exists('flavor') @@ -30,7 +31,7 @@ class SaharaNodeGroupTemplates(utils.SaharaScenario): def create_and_list_node_group_templates(self, flavor, plugin_name="vanilla", hadoop_version="1.2.1"): - """Test the sahara Node Group Templates create and list commands. + """Create and list Sahara Node Group Templates. This scenario creates two Node Group Templates with different set of node processes. The master Node Group Template contains Hadoop's @@ -42,11 +43,11 @@ class SaharaNodeGroupTemplates(utils.SaharaScenario): After the templates are created the list operation is called. - :param flavor: The Nova flavor that will be for nodes in the - created node groups - :param plugin_name: The name of a provisioning plugin - :param hadoop_version: The version of Hadoop distribution supported by - the specified plugin. + :param flavor: Nova flavor that will be for nodes in the + created node groups + :param plugin_name: name of a provisioning plugin + :param hadoop_version: version of Hadoop distribution supported by + the specified plugin. """ self._create_master_node_group_template(flavor_id=flavor, @@ -65,7 +66,7 @@ class SaharaNodeGroupTemplates(utils.SaharaScenario): def create_delete_node_group_templates(self, flavor, plugin_name="vanilla", hadoop_version="1.2.1"): - """Test create and delete commands. + """Create and delete Sahara Node Group Templates. This scenario creates and deletes two most common types of Node Group Templates. @@ -73,11 +74,11 @@ class SaharaNodeGroupTemplates(utils.SaharaScenario): By default the templates are created for the vanilla Hadoop provisioning plugin using the version 1.2.1 - :param flavor: The Nova flavor that will be for nodes in the - created node groups - :param plugin_name: The name of a provisioning plugin - :param hadoop_version: The version of Hadoop distribution supported by - the specified plugin. + :param flavor: Nova flavor that will be for nodes in the + created node groups + :param plugin_name: name of a provisioning plugin + :param hadoop_version: version of Hadoop distribution supported by + the specified plugin. """ master_ngt = self._create_master_node_group_template( diff --git a/rally/benchmark/scenarios/sahara/utils.py b/rally/benchmark/scenarios/sahara/utils.py index 4766ee14e9..74be79dc8d 100644 --- a/rally/benchmark/scenarios/sahara/utils.py +++ b/rally/benchmark/scenarios/sahara/utils.py @@ -40,6 +40,7 @@ CONF.register_opts(TIMEOUT_OPTS, group=benchmark_group) class SaharaScenario(base.Scenario): + """Base class for Sahara scenarios with basic atomic actions.""" RESOURCE_NAME_LENGTH = 20 @@ -110,22 +111,20 @@ class SaharaScenario(base.Scenario): @base.atomic_action_timer('sahara.list_node_group_templates') def _list_node_group_templates(self): - """Returns user Node Group Templates list.""" - + """Return user Node Group Templates list.""" return self.clients("sahara").node_group_templates.list() @base.atomic_action_timer('sahara.create_master_node_group_template') def _create_master_node_group_template(self, flavor_id, plugin_name, hadoop_version): - """Creates a master Node Group Template with a random name. + """Create a master Node Group Template with a random name. :param flavor_id: The required argument for the Template :param plugin_name: Sahara provisioning plugin name :param hadoop_version: The version of Hadoop distribution supported by - the plugin - :return: The created Template + the plugin + :returns: The created Template """ - name = self._generate_random_name(prefix="master-ngt-") return self.clients("sahara").node_group_templates.create( @@ -139,15 +138,14 @@ class SaharaScenario(base.Scenario): @base.atomic_action_timer('sahara.create_worker_node_group_template') def _create_worker_node_group_template(self, flavor_id, plugin_name, hadoop_version): - """Creates a worker Node Group Template with a random name. + """Create a worker Node Group Template with a random name. :param flavor_id: The required argument for the Template :param plugin_name: Sahara provisioning plugin name :param hadoop_version: The version of Hadoop distribution supported by - the plugin - :return: The created Template + the plugin + :returns: The created Template """ - name = self._generate_random_name(prefix="worker-ngt-") return self.clients("sahara").node_group_templates.create( @@ -160,12 +158,10 @@ class SaharaScenario(base.Scenario): @base.atomic_action_timer('sahara.delete_node_group_template') def _delete_node_group_template(self, node_group): - """Deletes a Node Group Template by id. + """Delete a Node Group Template by id. :param node_group: The Node Group Template to be deleted - :return: """ - self.clients("sahara").node_group_templates.delete(node_group.id) def _wait_active(self, cluster_object): @@ -182,39 +178,38 @@ class SaharaScenario(base.Scenario): volumes_size=None, auto_security_group=None, security_groups=None, node_configs=None, cluster_configs=None, wait_active=True): - """Creates a cluster and wait until it becomes Active. + """Create a cluster and wait until it becomes Active. The cluster is created with two node groups. The master Node Group is created with one instance. The worker node group contains node_count - 1 instances. - :param plugin_name: The provisioning plugin name + :param plugin_name: provisioning plugin name :param hadoop_version: Hadoop version supported by the plugin - :param flavor_id: The flavor which will be used to create instances - :param image_id: The image id that will be used to boot instances - :param node_count: The total number of instances. 1 master node, others - for the workers - :param floating_ip_pool: The floating ip pool name from which Floating - IPs will be allocated - :param neutron_net_id: The network id to allocate Fixed IPs - from, when Neutron is enabled for networking - :param volumes_per_node: The number of Cinder volumes that will be - attached to every cluster node - :param volumes_size: The size of each Cinder volume in GB - :param auto_security_group: Boolean value. If set to True Sahara will - create a Security Group for each Node Group in the Cluster - automatically. - :param security_groups: The list of security groups that will be used - while creating VMs. If auto_security_group is set to True this list - can be left empty. - :param node_configs: The configs dict that will be passed to each Node - Group - :param cluster_configs: The configs dict that will be passed to the - Cluster + :param flavor_id: flavor which will be used to create instances + :param image_id: image id that will be used to boot instances + :param node_count: total number of instances. 1 master node, others + for the workers + :param floating_ip_pool: floating ip pool name from which Floating + IPs will be allocated + :param neutron_net_id: network id to allocate Fixed IPs + from, when Neutron is enabled for networking + :param volumes_per_node: number of Cinder volumes that will be + attached to every cluster node + :param volumes_size: size of each Cinder volume in GB + :param auto_security_group: boolean value. If set to True Sahara will + create a Security Group for each Node Group + in the Cluster automatically. + :param security_groups: list of security groups that will be used + while creating VMs. If auto_security_group is + set to True, this list can be left empty. + :param node_configs: configs dict that will be passed to each Node + Group + :param cluster_configs: configs dict that will be passed to the + Cluster :param wait_active: Wait until a Cluster gets int "Active" state - :return: The created cluster + :returns: created cluster """ - node_groups = [ { "name": "master-ng", @@ -313,7 +308,6 @@ class SaharaScenario(base.Scenario): There two specific scaling methods of up and down scaling which have different atomic timers. """ - worker_node_group = [g for g in cluster.node_groups if "worker" in g["name"]][0] scale_object = { @@ -330,34 +324,30 @@ class SaharaScenario(base.Scenario): @base.atomic_action_timer('sahara.scale_up') def _scale_cluster_up(self, cluster, delta): - """Adds a given number of worker nodes to the cluster. + """Add a given number of worker nodes to the cluster. :param cluster: The cluster to be scaled :param delta: The number of workers to be added. (A positive number is - expected here) + expected here) """ - self._scale_cluster(cluster, delta) @base.atomic_action_timer('sahara.scale_down') def _scale_cluster_down(self, cluster, delta): - """Removes a given number of worker nodes from the cluster. + """Remove a given number of worker nodes from the cluster. :param cluster: The cluster to be scaled :param delta: The number of workers to be removed. (A negative number - is expected here) + is expected here) """ - self._scale_cluster(cluster, delta) @base.atomic_action_timer('sahara.delete_cluster') def _delete_cluster(self, cluster): - """Calls a Cluster delete by id and waits for complete deletion. + """Delete cluster. - :param cluster: The Cluster to be deleted - :return: + :param cluster: cluster to delete """ - self.clients("sahara").clusters.delete(cluster.id) bench_utils.wait_for(resource=cluster.id, @@ -371,11 +361,10 @@ class SaharaScenario(base.Scenario): return True def _create_output_ds(self): - """Creates an output Data Source based on EDP context + """Create an output Data Source based on EDP context :return: The created Data Source """ - ds_type = self.context["sahara_output_conf"]["output_type"] url_prefix = self.context["sahara_output_conf"]["output_url_prefix"] @@ -394,7 +383,7 @@ class SaharaScenario(base.Scenario): def _run_job_execution(self, job_id, cluster_id, input_id, output_id, configs, job_idx): - """Runs a Job Execution and waits until it completes or fails. + """Run a Job Execution and wait until it completes or fails. The Job Execution is accepted as successful when Oozie reports "success" or "succeeded" status. The failure statuses are "failed" and @@ -409,11 +398,10 @@ class SaharaScenario(base.Scenario): :param input_id: The input Data Source id :param output_id: The output Data Source id :param configs: The config dict that will be passed as Job Execution's - parameters. + parameters. :param job_idx: The index of a job in a sequence """ - @base.atomic_action_timer('sahara.job_execution_%s' % job_idx) def run(self): job_execution = self.clients("sahara").job_executions.create( diff --git a/rally/benchmark/scenarios/tempest/tempest.py b/rally/benchmark/scenarios/tempest/tempest.py index e3236ecd5c..52aeaec296 100644 --- a/rally/benchmark/scenarios/tempest/tempest.py +++ b/rally/benchmark/scenarios/tempest/tempest.py @@ -20,13 +20,14 @@ from rally import consts class TempestScenario(base.Scenario): + """Benchmark scenarios that launch Tempest tests.""" @validation.tempest_tests_exists() @validation.required_openstack(admin=True) @base.scenario(context={"tempest": {}}) @utils.tempest_log_wrapper def single_test(self, test_name, log_file, tempest_conf=None): - """Launch a single test + """Launch a single Tempest test by its name. :param test_name: name of tempest scenario for launching :param log_file: name of file for junitxml results @@ -43,7 +44,7 @@ class TempestScenario(base.Scenario): @base.scenario(context={"tempest": {}}) @utils.tempest_log_wrapper def all(self, log_file, tempest_conf=None): - """Launch all discovered tests + """Launch all discovered Tempest tests by their names. :param log_file: name of file for junitxml results :param tempest_conf: User specified tempest.conf location @@ -57,7 +58,7 @@ class TempestScenario(base.Scenario): @base.scenario(context={"tempest": {}}) @utils.tempest_log_wrapper def set(self, set_name, log_file, tempest_conf=None): - """Launch one by one methods from the set + """Launch all Tempest tests from a given set. :param set_name: set name of tempest scenarios for launching :param log_file: name of file for junitxml results @@ -79,7 +80,7 @@ class TempestScenario(base.Scenario): @base.scenario(context={"tempest": {}}) @utils.tempest_log_wrapper def list_of_tests(self, test_names, log_file, tempest_conf=None): - """Launch all tests from given list + """Launch all Tempest tests from a given list of their names. :param test_names: list of tempest scenarios for launching :param log_file: name of file for junitxml results @@ -93,8 +94,9 @@ class TempestScenario(base.Scenario): @base.scenario(context={"tempest": {}}) @utils.tempest_log_wrapper def specific_regex(self, regex, log_file, tempest_conf=None): - """Launch all tests which match given regex + """Launch Tempest tests whose names match a given regular expression. + :param regex: regexp to match Tempest test names against :param log_file: name of file for junitxml results :param tempest_conf: User specified tempest.conf location """ diff --git a/rally/benchmark/scenarios/vm/utils.py b/rally/benchmark/scenarios/vm/utils.py index 5580fb1ebd..d526274c41 100644 --- a/rally/benchmark/scenarios/vm/utils.py +++ b/rally/benchmark/scenarios/vm/utils.py @@ -23,12 +23,18 @@ from rally import sshutils class VMScenario(base.Scenario): + """Base class for VM scenarios with basic atomic actions. + + VM scenarios are scenarios executed inside some launched VM instance. + """ @base.atomic_action_timer('vm.run_command') def run_action(self, ssh, interpreter, script): """Run command inside an instance. - This is a separate function so that only script execution is timed + This is a separate function so that only script execution is timed. + + :returns: tuple (exit_status, stdout, stderr) """ return ssh.execute(interpreter, stdin=open(script, "rb")) @@ -64,7 +70,7 @@ class VMScenario(base.Scenario): """Check if a server is attached to the specified network. :param server: The server object to consider - :param network: The name of the network to search for. + :param network: The name of the network to search for :raises: `ValueError` if server is not attached to network. """ diff --git a/rally/benchmark/scenarios/vm/vmtasks.py b/rally/benchmark/scenarios/vm/vmtasks.py index fad11dcab9..3ca4b47f37 100644 --- a/rally/benchmark/scenarios/vm/vmtasks.py +++ b/rally/benchmark/scenarios/vm/vmtasks.py @@ -27,6 +27,7 @@ from rally import exceptions class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, cinder_utils.CinderScenario): + """Benchmark scenarios that are to be run inside VM instances.""" def __init__(self, *args, **kwargs): super(VMTasks, self).__init__(*args, **kwargs) @@ -51,12 +52,14 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, use_floatingip=True, force_delete=False, **kwargs): - """Boot server, run a script that outputs JSON, delete server. + """Boot a server, run a script that outputs JSON, delete the server. + + Example Script in doc/samples/tasks/support/instance_dd_test.sh :param image: glance image name to use for the vm :param flavor: VM flavor name :param script: script to run on the server, must output JSON mapping - metric names to values. See sample script below. + metric names to values (see the sample script below) :param interpreter: The shell interpreter to use when running script :param username: User to SSH to instance as :param volume_args: volume args when boot VM from volume @@ -65,15 +68,12 @@ class VMTasks(nova_utils.NovaScenario, vm_utils.VMScenario, :param ip_version: Version of ip protocol to use for connection :param port: Port to use for SSH connection :param use_floatingip: Whether to associate a floating ip for - connection + connection :param force_delete: Whether to use force_delete for instances :returns: Dictionary containing two keys, data and errors. Data is JSON - data output by the script. Errors is raw data from the - script's standard error stream. - - - Example Script in doc/samples/tasks/support/instance_dd_test.sh + data output by the script. Errors is raw data from the + script's standard error stream. """ if volume_args: volume = self._create_volume(volume_args['size'], imageRef=None) diff --git a/rally/benchmark/scenarios/zaqar/basic.py b/rally/benchmark/scenarios/zaqar/basic.py index a6de81d655..5b26ba69c9 100644 --- a/rally/benchmark/scenarios/zaqar/basic.py +++ b/rally/benchmark/scenarios/zaqar/basic.py @@ -20,38 +20,38 @@ from rally.benchmark import validation class ZaqarBasic(zutils.ZaqarScenario): + """Benchmark scenarios for Zaqar.""" @validation.number("name_length", minval=10) @base.scenario(context={"cleanup": ["zaqar"]}) def create_queue(self, name_length=10, **kwargs): - """Creates Zaqar queue with random name + """Create a Zaqar queue with a random name. :param name_length: length of generated (random) part of name :param kwargs: other optional parameters to create queues like "metadata" """ - self._queue_create(name_length=name_length, **kwargs) @validation.number("name_length", minval=10) @base.scenario(context={"cleanup": ["zaqar"]}) def producer_consumer(self, name_length=10, min_msg_count=50, max_msg_count=200, **kwargs): - """Serial producer/consumer + """Serial message producer/consumer. Creates a Zaqar queue with random name, sends a set of messages - and then retrieves an iterator containing those + and then retrieves an iterator containing those. :param name_length: length of generated (random) part of name :param min_msg_count: min number of messages to be posted :param max_msg_count: max number of messages to be posted :param kwargs: other optional parameters to create queues like - 'metadata' + "metadata" """ queue = self._queue_create(name_length=name_length, **kwargs) msg_count = random.randint(min_msg_count, max_msg_count) - messages = [{'body': {'id': idx}, 'ttl': 360} for idx + messages = [{"body": {"id": idx}, "ttl": 360} for idx in range(msg_count)] self._messages_post(queue, messages, min_msg_count, max_msg_count) self._messages_list(queue) diff --git a/rally/benchmark/scenarios/zaqar/utils.py b/rally/benchmark/scenarios/zaqar/utils.py index 9f2c99ca09..d66012d3f6 100644 --- a/rally/benchmark/scenarios/zaqar/utils.py +++ b/rally/benchmark/scenarios/zaqar/utils.py @@ -16,23 +16,23 @@ from rally.benchmark.scenarios import base class ZaqarScenario(base.Scenario): + """Base class for Zaqar scenarios with basic atomic actions.""" @base.atomic_action_timer('zaqar.create_queue') def _queue_create(self, name_length=10, **kwargs): - """Creates Zaqar queue with random name + """Create a Zaqar queue with random name. :param name_length: length of generated (random) part of name - :param **kwargs: other optional parameters to create queues like - "metadata" + :param kwargs: other optional parameters to create queues like + "metadata" :returns: Zaqar queue instance """ - name = self._generate_random_name(length=name_length) return self.clients("zaqar").queue(name, **kwargs) @base.atomic_action_timer('zaqar.delete_queue') def _queue_delete(self, queue): - """Removes a Zaqar queue + """Removes a Zaqar queue. :param queue: queue to remove """ @@ -40,10 +40,12 @@ class ZaqarScenario(base.Scenario): queue.delete() def _messages_post(self, queue, messages, min_msg_count, max_msg_count): - """Post a list of messages to a given Zaqar queue + """Post a list of messages to a given Zaqar queue. :param queue: post the messages to queue :param messages: messages to post + :param min_msg_count: minimum number of messages + :param max_msg_count: maximum number of messages """ with base.AtomicAction(self, 'zaqar.post_between_%s_and_%s_messages' % (min_msg_count, max_msg_count)): @@ -51,10 +53,10 @@ class ZaqarScenario(base.Scenario): @base.atomic_action_timer('zaqar.list_messages') def _messages_list(self, queue): - """Gets messages from a given Zaqar queue + """Gets messages from a given Zaqar queue. :param queue: get messages from queue - :return: messages iterator + :returns: messages iterator """ return queue.messages() diff --git a/rally/sshutils.py b/rally/sshutils.py index 2b6abda2cb..826d1896e7 100644 --- a/rally/sshutils.py +++ b/rally/sshutils.py @@ -92,7 +92,6 @@ class SSH(object): :param pkey: RSA or DSS private key string or file object :param key_filename: private key filename :param password: password - """ self.user = user @@ -234,8 +233,7 @@ class SSH(object): :param stdin: Open file to be sent on process stdin. :param timeout: Timeout for execution of the command. - Return tuple (exit_status, stdout, stderr) - + :returns: tuple (exit_status, stdout, stderr) """ stdout = StringIO.StringIO() stderr = StringIO.StringIO() diff --git a/rally/utils.py b/rally/utils.py index 0be4e8f4b4..2a03164291 100644 --- a/rally/utils.py +++ b/rally/utils.py @@ -382,12 +382,12 @@ def distance(s1, s2): def retry(times, func, *args, **kwargs): - """Tries to execute multiple times function mitigating exceptions. + """Try to execute multiple times function mitigating exceptions. :param times: Amount of attempts to execute function :param func: Function that should be executed - :param *args: *args that are passed to func - :param **kwargs: **kwargs that are passed to func + :param args: *args that are passed to func + :param kwargs: **kwargs that are passed to func :raises: Raise any exception that can raise func :returns: Result of func(*args, **kwargs) diff --git a/tests/unit/benchmark/scenarios/nova/test_servers.py b/tests/unit/benchmark/scenarios/nova/test_servers.py index afa1a714d1..223df5b083 100644 --- a/tests/unit/benchmark/scenarios/nova/test_servers.py +++ b/tests/unit/benchmark/scenarios/nova/test_servers.py @@ -40,8 +40,7 @@ class NovaServersTestCase(test.TestCase): scenario._delete_server = mock.MagicMock() scenario.boot_and_bounce_server("img", 1, actions=actions) - scenario._boot_server.assert_called_once_with("name", "img", 1, - actions=actions) + scenario._boot_server.assert_called_once_with("name", "img", 1) server_calls = [] for i in range(5): server_calls.append(mock.call(fake_server)) @@ -66,8 +65,7 @@ class NovaServersTestCase(test.TestCase): scenario.boot_and_bounce_server("img", 1, actions=actions) - scenario._boot_server.assert_called_once_with("name", "img", 1, - actions=actions) + scenario._boot_server.assert_called_once_with("name", "img", 1) server_calls = [] for i in range(5): server_calls.append(mock.call(fake_server)) @@ -92,8 +90,7 @@ class NovaServersTestCase(test.TestCase): scenario._generate_random_name = mock.MagicMock(return_value='name') scenario.boot_and_bounce_server("img", 1, actions=actions) - scenario._boot_server.assert_called_once_with("name", "img", 1, - actions=actions) + scenario._boot_server.assert_called_once_with("name", "img", 1) server_calls = [] for i in range(5): server_calls.append(mock.call(fake_server)) @@ -146,8 +143,7 @@ class NovaServersTestCase(test.TestCase): scenario.boot_and_bounce_server("img", 1, actions=actions) - scenario._boot_server.assert_called_once_with("name", "img", 1, - actions=actions) + scenario._boot_server.assert_called_once_with("name", "img", 1) server_calls = [] for i in range(5): server_calls.append(mock.call(fake_server)) diff --git a/tests/unit/benchmark/sla/test_base.py b/tests/unit/benchmark/sla/test_base.py index 205364b5d9..519d7a44cf 100644 --- a/tests/unit/benchmark/sla/test_base.py +++ b/tests/unit/benchmark/sla/test_base.py @@ -21,6 +21,7 @@ from tests.unit import test class TestCriterion(base.SLA): + """Test SLA.""" OPTION_NAME = "test_criterion" CONFIG_SCHEMA = {"type": "integer"} diff --git a/tests/unit/deploy/serverprovider/test_provider.py b/tests/unit/deploy/serverprovider/test_provider.py index d154ed59d8..d4c92a7ac3 100644 --- a/tests/unit/deploy/serverprovider/test_provider.py +++ b/tests/unit/deploy/serverprovider/test_provider.py @@ -35,14 +35,26 @@ class ProviderMixIn(object): class ProviderA(ProviderMixIn, ProviderFactory): + """Fake server provider. + + Used for tests. + """ pass class ProviderB(ProviderMixIn, ProviderFactory): + """Fake server provider. + + Used for tests. + """ pass class ProviderC(ProviderB): + """Fake server provider. + + Used for tests. + """ pass diff --git a/tests/unit/deploy/test_engine.py b/tests/unit/deploy/test_engine.py index 8e85551bf7..b8b68b7e38 100644 --- a/tests/unit/deploy/test_engine.py +++ b/tests/unit/deploy/test_engine.py @@ -56,6 +56,10 @@ class FakeDeployment(object): class FakeEngine(deploy.EngineFactory): + """Fake deployment engine. + + Used for tests. + """ deployed = False cleanuped = False @@ -80,14 +84,26 @@ class EngineMixIn(object): class EngineFake1(EngineMixIn, deploy.EngineFactory): + """Fake deployment engine. + + Used for tests. + """ pass class EngineFake2(EngineMixIn, deploy.EngineFactory): + """Fake deployment engine. + + Used for tests. + """ pass class EngineFake3(EngineFake2): + """Fake deployment engine. + + Used for tests. + """ pass diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index 301a97a84f..22172f102c 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -1288,6 +1288,7 @@ class FakeRunner(object): class FakeScenario(base.Scenario): + """Fake Scenario class.""" def idle_time(self): return 0 diff --git a/tests/unit/test_docstrings.py b/tests/unit/test_docstrings.py new file mode 100644 index 0000000000..092277921e --- /dev/null +++ b/tests/unit/test_docstrings.py @@ -0,0 +1,102 @@ +# 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.benchmark.scenarios import base +from rally.benchmark.sla import base as sla_base +from rally import deploy +from rally.deploy import serverprovider +from rally import utils +from tests.unit import test + + +class DocstringsTestCase(test.TestCase): + + def test_all_scenarios_have_docstrings(self): + ignored_params = ["self", "scenario_obj"] + for scenario_group in utils.itersubclasses(base.Scenario): + for method in dir(scenario_group): + if base.Scenario.is_scenario(scenario_group, method): + scenario = getattr(scenario_group, method) + scenario_name = scenario_group.__name__ + "." + method + self.assertIsNotNone(scenario.__doc__, + "%s doensn't have a docstring." % + scenario_name) + doc = utils.parse_docstring(scenario.__doc__) + short_description = doc["short_description"] + self.assertIsNotNone(short_description, + "Docstring for %s should have " + "at least a one-line description." % + scenario_name) + self.assertFalse(short_description.startswith("Test"), + "One-line description for %s " + "should be declarative and not start " + "with 'Test(s) ...'" % scenario_name) + params_count = scenario.func_code.co_argcount + params = scenario.func_code.co_varnames[:params_count] + documented_params = [p["name"] for p in doc["params"]] + for param in params: + if param not in ignored_params: + self.assertIn(param, documented_params, + "Docstring for %(scenario)s should " + "describe the '%(param)s' parameter " + "in the :param <name>: clause." % + {"scenario": scenario_name, + "param": param}) + + def test_all_scenario_groups_have_docstrings(self): + for scenario_group in utils.itersubclasses(base.Scenario): + scenario_group_name = scenario_group.__name__ + self.assertIsNotNone(scenario_group.__doc__, + "%s doesn't have a class-level docstring." % + scenario_group_name) + doc = utils.parse_docstring(scenario_group.__doc__) + msg = ("Docstring for %s should have a one-line description." % + scenario_group_name) + self.assertIsNotNone(doc["short_description"], msg) + + def test_all_deploy_engines_have_docstrings(self): + for deploy_engine in utils.itersubclasses(deploy.EngineFactory): + deploy_engine_name = deploy_engine.__name__ + self.assertIsNotNone(deploy_engine.__doc__, + "%s doesn't have a class-level docstring." % + deploy_engine_name) + doc = utils.parse_docstring(deploy_engine.__doc__) + msg = ("Docstring for %s should have a one-line description " + "and a detailed description." % deploy_engine_name) + self.assertIsNotNone(doc["short_description"], msg) + self.assertIsNotNone(doc["long_description"], msg) + + def test_all_server_providers_have_docstrings(self): + for provider in utils.itersubclasses(serverprovider.ProviderFactory): + provider_name = provider.__name__ + self.assertIsNotNone(provider.__doc__, + "%s doesn't have a class-level docstring." % + provider_name) + doc = utils.parse_docstring(provider.__doc__) + msg = ("Docstring for %s should have a one-line description " + "and a detailed description." % provider_name) + self.assertIsNotNone(doc["short_description"], msg) + self.assertIsNotNone(doc["long_description"], msg) + + def test_all_SLA_have_docstrings(self): + for sla in utils.itersubclasses(sla_base.SLA): + sla_name = sla.OPTION_NAME + self.assertIsNotNone(sla.__doc__, + "%s doesn't have a class-level docstring." % + sla_name) + doc = utils.parse_docstring(sla.__doc__) + self.assertIsNotNone(doc["short_description"], + "Docstring for %s should have a " + "one-line description." % sla_name)