diff --git a/rally_openstack/contexts/dataplane/heat.py b/rally_openstack/contexts/dataplane/heat.py index db5bdff3..e3a65ca5 100644 --- a/rally_openstack/contexts/dataplane/heat.py +++ b/rally_openstack/contexts/dataplane/heat.py @@ -39,16 +39,19 @@ class HeatDataplane(context.Context): This context will create stacks by given template for each tenant and add details to context. Following details will be added: - id of stack; - template file contents; - files dictionary; - stack parameters; + + * id of stack; + * template file contents; + * files dictionary; + * stack parameters; + Heat template should define a "gate" node which will interact with Rally by ssh and workload nodes by any protocol. To make this possible heat template should accept the following parameters: - network_id: id of public network - router_id: id of external router to connect "gate" node - key_name: name of nova ssh keypair to use for "gate" node + + * network_id: id of public network + * router_id: id of external router to connect "gate" node + * key_name: name of nova ssh keypair to use for "gate" node """ FILE_SCHEMA = { "description": "", diff --git a/rally_openstack/hook/fault_injection.py b/rally_openstack/hook/fault_injection.py index 35c0aa67..b645f556 100644 --- a/rally_openstack/hook/fault_injection.py +++ b/rally_openstack/hook/fault_injection.py @@ -27,8 +27,9 @@ class FaultInjectionHook(hook.Hook): """Performs fault injection using os-faults library. Configuration: - action - string that represents an action (more info in [1]) - verify - whether to verify connection to cloud nodes or not + + * action - string that represents an action (more info in [1]) + * verify - whether to verify connection to cloud nodes or not This plugin discovers extra config of ExistingCloud and looks for "cloud_config" field. If cloud_config is present then diff --git a/rally_openstack/scenarios/ceilometer/alarms.py b/rally_openstack/scenarios/ceilometer/alarms.py index 62601d28..b3f8097b 100644 --- a/rally_openstack/scenarios/ceilometer/alarms.py +++ b/rally_openstack/scenarios/ceilometer/alarms.py @@ -171,10 +171,12 @@ class CreateAlarmAndGetHistory(ceiloutils.CeilometerScenario): def run(self, meter_name, threshold, state, timeout=60, **kwargs): """Create an alarm, get and set the state and get the alarm history. - This scenario makes following queries: - GET /v2/alarms/{alarm_id}/history - GET /v2/alarms/{alarm_id}/state - PUT /v2/alarms/{alarm_id}/state + This scenario makes following queries: + + * GET /v2/alarms/{alarm_id}/history + * GET /v2/alarms/{alarm_id}/state + * PUT /v2/alarms/{alarm_id}/state + Initially alarm is created and then get the state of the created alarm using its alarm_id. Then get the history of the alarm. And finally the state of the alarm is updated using given state. meter_name and diff --git a/rally_openstack/scenarios/cinder/volume_types.py b/rally_openstack/scenarios/cinder/volume_types.py index 45dec8f5..e5cac91a 100644 --- a/rally_openstack/scenarios/cinder/volume_types.py +++ b/rally_openstack/scenarios/cinder/volume_types.py @@ -139,8 +139,8 @@ class CreateVolumeTypeAndEncryptionType(cinder_utils.CinderBasic): is_public=True): """Create encryption type - This scenario first creates a volume type, then creates an encryption - type for the volume type. + This scenario first creates a volume type, then creates an encryption + type for the volume type. :param create_specs: The encryption type specifications to add. DEPRECATED, specify arguments explicitly. @@ -186,9 +186,9 @@ class CreateAndListEncryptionType(cinder_utils.CinderBasic): key_size=None, control_location="front-end", search_opts=None): """Create and list encryption type - This scenario firstly creates a volume type, secondly creates an - encryption type for the volume type, thirdly lists all encryption - types. + This scenario firstly creates a volume type, secondly creates an + encryption type for the volume type, thirdly lists all encryption + types. :param create_specs: The encryption type specifications to add. DEPRECATED, specify arguments explicitly. @@ -254,10 +254,10 @@ class CreateGetAndDeleteEncryptionType(cinder_utils.CinderBasic): key_size=None, control_location="front-end"): """Create get and delete an encryption type - This scenario firstly creates an encryption type for a volome - type created in the context, then gets detailed information of - the created encryption type, finally deletes the created - encryption type. + This scenario firstly creates an encryption type for a volome + type created in the context, then gets detailed information of + the created encryption type, finally deletes the created + encryption type. :param provider: The class that provides encryption support. For example, LuksEncryptor. @@ -295,8 +295,8 @@ class CreateAndDeleteEncryptionType(cinder_utils.CinderBasic): key_size=None, control_location="front-end"): """Create and delete encryption type - This scenario firstly creates an encryption type for a given - volume type, then deletes the created encryption type. + This scenario firstly creates an encryption type for a given + volume type, then deletes the created encryption type. :param create_specs: the encryption type specifications to add :param provider: The class that provides encryption support. For @@ -340,9 +340,9 @@ class CreateAndUpdateEncryptionType(cinder_utils.CinderBasic): update_key_size=None, update_control_location=None): """Create and update encryption type - This scenario firstly creates a volume type, secondly creates an - encryption type for the volume type, thirdly updates the encryption - type. + This scenario firstly creates a volume type, secondly creates an + encryption type for the volume type, thirdly updates the encryption + type. :param create_provider: The class that provides encryption support. For example, LuksEncryptor. @@ -391,8 +391,8 @@ class CreateVolumeTypeAddAndListTypeAccess(scenario.OpenStackScenario): def run(self, description=None, is_public=False): """Add and list volume type access for the given project. - This scenario first creates a private volume type, then add project - access and list project access to it. + This scenario first creates a private volume type, then add project + access and list project access to it. :param description: Description of the volume type :param is_public: Volume type visibility diff --git a/rally_openstack/scenarios/cinder/volumes.py b/rally_openstack/scenarios/cinder/volumes.py index 9081271d..faea07a3 100644 --- a/rally_openstack/scenarios/cinder/volumes.py +++ b/rally_openstack/scenarios/cinder/volumes.py @@ -757,8 +757,9 @@ class CreateVolumeAndClone(cinder_utils.CinderBasic): def run(self, size, image=None, nested_level=1, **kwargs): """Create a volume, then clone it to another volume. - This creates a volume, then clone it to anothor volume, - and then clone the new volume to next volume... + This creates a volume, then clone it to anothor volume, + and then clone the new volume to next volume... + 1. create source volume (from image) 2. clone source volume to volume1 3. clone volume1 to volume2 diff --git a/rally_openstack/scenarios/nova/keypairs.py b/rally_openstack/scenarios/nova/keypairs.py index e8c8ec7a..543de696 100644 --- a/rally_openstack/scenarios/nova/keypairs.py +++ b/rally_openstack/scenarios/nova/keypairs.py @@ -85,10 +85,11 @@ class BootAndDeleteServerWithKeypair(utils.NovaScenario): """Boot and delete server with keypair. Plan of this scenario: - - create a keypair - - boot a VM with created keypair - - delete server - - delete keypair + + - create a keypair + - boot a VM with created keypair + - delete server + - delete keypair :param image: ID of the image to be used for server creation :param flavor: ID of the flavor to be used for server creation diff --git a/rally_openstack/scenarios/vm/vmtasks.py b/rally_openstack/scenarios/vm/vmtasks.py index f18b4bc4..2b65efd5 100644 --- a/rally_openstack/scenarios/vm/vmtasks.py +++ b/rally_openstack/scenarios/vm/vmtasks.py @@ -46,7 +46,7 @@ class ValidCommandValidator(validators.FileExistsValidator): """Checks that parameter is a proper command-specifying dictionary. Ensure that the command dictionary is a proper command-specifying - dictionary described in `vmtasks.VMTasks.boot_runcommand_delete' + dictionary described in 'vmtasks.VMTasks.boot_runcommand_delete' docstring. :param param_name: Name of parameter to validate @@ -500,7 +500,7 @@ class DDLoadTest(BootRuncommandDelete): max_log_length=None, **kwargs): """Boot a server from a custom image and performs dd load test. - NOTE: dd load test is prepared script by Rally team. It checks + .. note:: dd load test is prepared script by Rally team. It checks writing and reading metrics from the VM. :param image: glance image name to use for the vm. Optional diff --git a/rally_openstack/verification/tempest/manager.py b/rally_openstack/verification/tempest/manager.py index 76eaa2da..06523a69 100644 --- a/rally_openstack/verification/tempest/manager.py +++ b/rally_openstack/verification/tempest/manager.py @@ -49,15 +49,15 @@ class TempestManager(testr.TestrLauncher): Rally supports features listed below: - * *cloning Tempest*: repository and version can be specified - * *installation*: system-wide with checking existence of required - packages or in virtual environment - * *configuration*: options are discovered via OpenStack API, but you can - override them if you need - * *running*: pre-creating all required resources(i.e images, tenants, - etc), prepare arguments, launching Tempest, live-progress output - * *results*: all verifications are stored in db, you can built reports, - compare verification at whatever you want time. + * *cloning Tempest*: repository and version can be specified + * *installation*: system-wide with checking existence of required + packages or in virtual environment + * *configuration*: options are discovered via OpenStack API, but you can + override them if you need + * *running*: pre-creating all required resources(i.e images, tenants, + etc), prepare arguments, launching Tempest, live-progress output + * *results*: all verifications are stored in db, you can built reports, + compare verification at whatever you want time. Appeared in Rally 0.8.0 *(actually, it appeared long time ago with first revision of Verification Component, but 0.8.0 is mentioned since it is diff --git a/tests/unit/doc/test_docstrings.py b/tests/unit/doc/test_docstrings.py index b2958662..64261ce6 100644 --- a/tests/unit/doc/test_docstrings.py +++ b/tests/unit/doc/test_docstrings.py @@ -13,7 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from rally.common.plugin import info +from docutils import frontend +from docutils import nodes +from docutils.parsers import rst +from docutils import utils + from rally.common.plugin import plugin from rally.common import validation from rally import plugins @@ -21,6 +25,15 @@ from rally.task import scenario from tests.unit import test +def _parse_rst(text): + parser = rst.Parser() + settings = frontend.OptionParser( + components=(rst.Parser,)).get_default_values() + document = utils.new_document(text, settings) + parser.parse(text, document) + return document.children + + class DocstringsTestCase(test.TestCase): def setUp(self): @@ -47,20 +60,50 @@ class DocstringsTestCase(test.TestCase): result.append(msg) return result + # the list with plugins names which use rst definitions in their docstrings + _HAS_VALID_DEFINITIONS = [] + + def _validate_rst(self, plugin_name, text, msg_buffer): + # need to wait till the next release of rally + return + parsed_docstring = _parse_rst(text) + for item in parsed_docstring: + if (isinstance(item, nodes.definition_list) + and plugin_name not in self._HAS_VALID_DEFINITIONS): + msg_buffer.append("Plugin %s has a docstring with invalid " + "format. Re-check intend and required empty " + "lines between the list title and list " + "items." % plugin_name) + elif isinstance(item, nodes.system_message): + msg_buffer.append( + "A warning is caught while parsing docstring of '%s' " + "plugin: %s" % (plugin_name, item.astext())) + def _check_docstrings(self, msg_buffer): for plg_cls in plugin.Plugin.get_all(): - if plg_cls.__module__.startswith("rally."): - doc = info.parse_docstring(plg_cls.__doc__) - short_description = doc["short_description"] - if short_description.startswith("Test"): - msg_buffer.append("One-line description for %s" - " should be declarative and not" - " start with 'Test(s) ...'" - % plg_cls.__name__) - if not plg_cls.get_info()["title"]: - msg = "Class '{}.{}' should have a docstring." - msg_buffer.append(msg.format(plg_cls.__module__, - plg_cls.__name__)) + if not plg_cls.__module__.startswith("rally_openstack."): + continue + name = "%s (%s.%s)" % (plg_cls.get_name(), + plg_cls.__module__, + plg_cls.__name__) + doc_info = plg_cls.get_info() + if not doc_info["title"]: + msg_buffer.append("Plugin '%s' should have a docstring." + % name) + if doc_info["title"].startswith("Test"): + msg_buffer.append("One-line description for %s" + " should be declarative and not" + " start with 'Test(s) ...'" + % name) + + # NOTE(andreykurilin): I never saw any real usage of + # reStructuredText definitions in our docstrings. In most cases, + # "definitions" means that there is an issue with intends or + # missed empty line before the list title and list items. + if doc_info["description"]: + self._validate_rst(plg_cls.get_name(), + doc_info["description"], + msg_buffer) def _check_described_params(self, msg_buffer): for plg_cls in plugin.Plugin.get_all(): @@ -81,4 +124,4 @@ class DocstringsTestCase(test.TestCase): self._check_described_params(msg_buffer) if msg_buffer: - self.fail("\n%s" % "\n".join(msg_buffer)) + self.fail("\n%s" % "\n===============\n".join(msg_buffer))