diff options
| author | Doug Hellmann <doug@doughellmann.com> | 2017-06-13 15:55:33 -0400 |
|---|---|---|
| committer | Andreas Jaeger <aj@suse.com> | 2017-06-23 11:54:32 +0200 |
| commit | 9599ffe65d9dcd4b3aa780d346eccd1e760890bf (patch) | |
| tree | 9281e521e50b8bed66eca087bc11fa03adf2aed3 /doc/source/contributor | |
| parent | 19c8cabeca1ea3c83da734ab5269318b27eb5634 (diff) | |
| download | python-openstackclient-9599ffe65d9dcd4b3aa780d346eccd1e760890bf.tar.gz | |
reorganize existing documentation according to the new standard layout
Move existing content around based on the doc-migration specification.
Replace :doc: markup with :ref: to have sphinx keep track of where the
files move and generate valid hyperlinks.
Add a few toctrees and index pages for the new directories.
Depends-On: Ia750cb049c0f53a234ea70ce1f2bbbb7a2aa9454
Change-Id: I253ee8f89d3ec40e39310c18bb87ed1d3d5de330
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
Diffstat (limited to 'doc/source/contributor')
| -rw-r--r-- | doc/source/contributor/command-beta.rst | 108 | ||||
| -rw-r--r-- | doc/source/contributor/command-errors.rst | 202 | ||||
| -rw-r--r-- | doc/source/contributor/command-logs.rst | 73 | ||||
| -rw-r--r-- | doc/source/contributor/command-options.rst | 312 | ||||
| -rw-r--r-- | doc/source/contributor/command-wrappers.rst | 52 | ||||
| -rw-r--r-- | doc/source/contributor/developing.rst | 209 | ||||
| -rw-r--r-- | doc/source/contributor/humaninterfaceguide.rst | 368 | ||||
| -rw-r--r-- | doc/source/contributor/index.rst | 16 | ||||
| -rw-r--r-- | doc/source/contributor/plugins.rst | 245 | ||||
| -rw-r--r-- | doc/source/contributor/specs/command-objects/example.rst | 86 | ||||
| -rw-r--r-- | doc/source/contributor/specs/commands.rst | 44 | ||||
| -rwxr-xr-x | doc/source/contributor/specs/network-topology.rst | 44 |
12 files changed, 1759 insertions, 0 deletions
diff --git a/doc/source/contributor/command-beta.rst b/doc/source/contributor/command-beta.rst new file mode 100644 index 00000000..40ede671 --- /dev/null +++ b/doc/source/contributor/command-beta.rst @@ -0,0 +1,108 @@ +.. _command-beta: + +============ +Command Beta +============ + +OpenStackClient releases do not always coincide with OpenStack +releases. This creates challenges when developing new OpenStackClient +commands for the current OpenStack release under development +since there may not be an official release of the REST API +enhancements necessary for the command. In addition, backwards +compatibility may not be guaranteed until an official OpenStack release. +To address these challenges, an OpenStackClient command may +be labeled as a beta command according to the guidelines +below. Such commands may introduce backwards incompatible +changes and may use REST API enhancements not yet released. +This also applies to command options associated with the beta +command object. + +See the examples below on how to label an entire command or +a specific option as a beta by updating the documentation +and implementation. + +The initial release note must label the new command or option +as a beta. No further release notes are required until the command +or option is no longer a beta. At which time, the beta label or +the command or option itself must be removed and a new release note +must be provided. + +Beta Command Example +-------------------- + +Documentation +~~~~~~~~~~~~~ + +The command documentation must label the command as a beta. + +example list +++++++++++++ + +List examples + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: example list +.. code:: bash + + openstack example list + +Help +~~~~ + +The command help must label the command as a beta. + +.. code-block:: python + + class ShowExample(command.ShowOne): + """Display example details + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + +Implementation +~~~~~~~~~~~~~~ + +The command must raise a ``CommandError`` exception if beta commands +are not enabled via ``--os-beta-command`` global option. + +.. code-block:: python + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + +Beta Option Example +------------------- + +Documentation +~~~~~~~~~~~~~ + +The option documentation must label the option as a beta. + +.. option:: --example <example> + + Example + + .. caution:: This is a beta command option and subject + to change. Use global option ``--os-beta-command`` + to enable this command option. + +Implementation +~~~~~~~~~~~~~~ + +The option must not be added if beta commands are not +enabled via ``--os-beta-command`` global option. + +.. code-block:: python + + def get_parser(self, prog_name): + if self.app.options.os_beta_command: + parser.add_argument( + '--example', + metavar='<example>', + help=_("Example") + ) diff --git a/doc/source/contributor/command-errors.rst b/doc/source/contributor/command-errors.rst new file mode 100644 index 00000000..c4adb7d1 --- /dev/null +++ b/doc/source/contributor/command-errors.rst @@ -0,0 +1,202 @@ +============== +Command Errors +============== + +Handling errors in OpenStackClient commands is fairly straightforward. An +exception is thrown and handled by the application-level caller. + +Note: There are many cases that need to be filled out here. The initial +version of this document considers the general command error handling as well +as the specific case of commands that make multiple REST API calls and how to +handle when one or more of those calls fails. + +General Command Errors +====================== + +The general pattern for handling OpenStackClient command-level errors is to +raise a CommandError exception with an appropriate message. This should include +conditions arising from arguments that are not valid/allowed (that are not otherwise +enforced by ``argparse``) as well as errors arising from external conditions. + +External Errors +--------------- + +External errors are a result of things outside OpenStackClient not being as +expected. + +Example +~~~~~~~ + +This example is taken from ``keypair create`` where the ``--public-key`` option +specifies a file containing the public key to upload. If the file is not found, +the IOError exception is trapped and a more specific CommandError exception is +raised that includes the name of the file that was attempted to be opened. + +.. code-block:: python + + class CreateKeypair(command.ShowOne): + """Create new public key""" + + ## ... + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + public_key = parsed_args.public_key + if public_key: + try: + with io.open( + os.path.expanduser(parsed_args.public_key), + "rb" + ) as p: + public_key = p.read() + except IOError as e: + msg = _("Key file %s not found: %s") + raise exceptions.CommandError( + msg % (parsed_args.public_key, e), + ) + + keypair = compute_client.keypairs.create( + parsed_args.name, + public_key=public_key, + ) + + ## ... + +REST API Errors +=============== + +Most commands make a single REST API call via the supporting client library +or SDK. Errors based on HTML return codes are usually handled well by default, +but in some cases more specific or user-friendly messages need to be logged. +Trapping the exception and raising a CommandError exception with a useful +message is the correct approach. + +Multiple REST API Calls +----------------------- + +Some CLI commands make multiple calls to library APIs and thus REST APIs. +Most of the time these are ``create`` or ``set`` commands that expect to add or +change a resource on the server. When one of these calls fails, the behaviour +of the remainder of the command handler is defined as such: + +* Whenever possible, all API calls will be made. This may not be possible for + specific commands where the subsequent calls are dependent on the results of + an earlier call. + +* Any failure of an API call will be logged for the user + +* A failure of any API call results in a non-zero exit code + +* In the cases of failures in a ``create`` command a follow-up mode needs to + be present that allows the user to attempt to complete the call, or cleanly + remove the partially-created resource and re-try. + +The desired behaviour is for commands to appear to the user as idempotent +whenever possible, i.e. a partial failure in a ``set`` command can be safely +retried without harm. ``create`` commands are a harder problem and may need +to be handled by having the proper options in a set command available to allow +recovery in the case where the primary resource has been created but the +subsequent calls did not complete. + +Example 1 +~~~~~~~~~ + +This example is taken from the ``volume snapshot set`` command where ``--property`` +arguments are set using the volume manager's ``set_metadata()`` method, +``--state`` arguments are set using the ``reset_state()`` method, and the +remaining arguments are set using the ``update()`` method. + +.. code-block:: python + + class SetSnapshot(command.Command): + """Set snapshot properties""" + + ## ... + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, + parsed_args.snapshot, + ) + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, + parsed_args.property, + ) + except SomeException: # Need to define the exceptions to catch here + LOG.error(_("Property set failed")) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, + parsed_args.state, + ) + except SomeException: # Need to define the exceptions to catch here + LOG.error(_("State set failed")) + result += 1 + + try: + volume_client.volume_snapshots.update( + snapshot.id, + **kwargs + ) + except SomeException: # Need to define the exceptions to catch here + LOG.error(_("Update failed")) + result += 1 + + # NOTE(dtroyer): We need to signal the error, and a non-zero return code, + # without aborting prematurely + if result > 0: + raise SomeNonFatalException + +Example 2 +~~~~~~~~~ + +This example is taken from the ``network delete`` command which takes multiple +networks to delete. All networks will be deleted in a loop, which makes +multiple ``delete_network()`` calls. + +.. code-block:: python + + class DeleteNetwork(common.NetworkAndComputeCommand): + """Delete network(s)""" + + def update_parser_common(self, parser): + parser.add_argument( + 'network', + metavar="<network>", + nargs="+", + help=_("Network(s) to delete (name or ID)") + ) + return parser + + def take_action(self, client, parsed_args): + ret = 0 + + for network in parsed_args.network: + try: + obj = client.find_network(network, ignore_missing=False) + client.delete_network(obj) + except Exception: + LOG.error(_("Failed to delete network with name " + "or ID %s."), network) + ret += 1 + + if ret > 0: + total = len(parsed_args.network) + msg = (_("Failed to delete %(ret)s of %(total)s networks.") + % {"ret": ret, "total": total}) + raise exceptions.CommandError(msg) diff --git a/doc/source/contributor/command-logs.rst b/doc/source/contributor/command-logs.rst new file mode 100644 index 00000000..62126510 --- /dev/null +++ b/doc/source/contributor/command-logs.rst @@ -0,0 +1,73 @@ +============ +Command Logs +============ + +Logger usage in OpenStackClient is not exactly the same as those in other +OpenStack projects. The following basic rules should be followed. + +1. OpenStackClient uses python standard logging library instead of oslo.log + so that it will depend on oslo as little as possible. + +2. All logs except debug log need to be translated. The log message strings + that need to be translated should follow the rule of i18n guidelines: + http://docs.openstack.org/developer/oslo.i18n/guidelines.html + +3. There are mainly two kinds of logs in OpenStackClient: command specific + log and general log. Use different logger to record them. The examples + below will show the detail. + +Command specific log +==================== + +Command specific logs are those messages that used to record info, warning +and error generated from a specific command. OpenStackClient uses the logger +of the module the command belongs to to record the command specific logs. + +Example +~~~~~~~ + +This example shows how to log command specific logs in OpenStackClient. + +.. code-block:: python + + import logging + + from openstackclient.i18n import _ + + + LOG = logging.getLogger(__name__) # Get the logger of this module + + ## ... + + LOG.error(_("Error message")) + LOG.warning(_("Warning message")) + LOG.info(_("Info message")) + LOG.debug("Debug message") # Debug messages do not need to be translated + + ## ... + +General log +=========== + +General logs are those messages that not specific to any single command. Use +the logger of ``openstackclient.shell`` to record them. In each command class, +we can simply get this logger by ``self.app.log``. + +Example +~~~~~~~ + +This example shows how to log general logs in OpenStackClient. + +.. code-block:: python + + from openstackclient.i18n import _ + + + ## ... + + self.app.log.error(_("Error message")) + self.app.log.warning(_("Warning message")) + self.app.log.info(_("Info message")) + self.app.log.debug("Debug message") # Debug messages do not need to be translated + + ## ... diff --git a/doc/source/contributor/command-options.rst b/doc/source/contributor/command-options.rst new file mode 100644 index 00000000..06623445 --- /dev/null +++ b/doc/source/contributor/command-options.rst @@ -0,0 +1,312 @@ +=============== +Command Options +=============== + +OpenStackClient commands all have a set of zero or more options unique to +the command, however there are of course ways in which these options are +common and consistent across all of the commands that include them. + +These are the set of guidelines for OSC developers that help keep the +interface and commands consistent. + +In some cases (like the boolean variables below) we use the same pattern +for defining and using options in all situations. The alternative of only +using it when necessary leads to errors when copy-n-paste is used for a +new command without understanding why or why not that instance is correct. + +The :ref:`hig` describes the guildelines for option names and usage. +In short: + +* All option names shall be GNU-style long names (two leading dashes). +* Some global options may have short names, generally limited to those defined + in support libraries such as ``cliff``. + +General Command Options +======================= + +Boolean Options +--------------- + +Boolean options for any command that sets a resource state, such as 'enabled' +or 'public', shall always have both positive and negative options defined. +The names of those options shall either be a naturally occurring pair of +words (in English) or a positive option and a negative option with `no-` +prepended (such as in the traditional GNU option usage) like `--share` and +`--no-share`. + +In order to handle those APIs that behave differently when a field is set to +`None` and when the field is not present in a passed argument list or dict, +each of the boolean options shall set its own variable to `True` as part of +a mutually exclusive group, rather than the more common configuration of +setting a single destination variable `True` or `False` directly. This allows +us to detect the situation when neither option is present (both variables will +be `False`) and act accordingly for those APIs where this matters. + +This also requires that each of the boolean values be tested in the +`take_action()` method to correctly set (or not) the underlying API field +values. + +.. option:: --enable + + Enable <resource> (default) + +.. option:: --disable + + Disable <resource> + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this: + +.. code-block:: python + + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_('Enable <resource> (default)'), + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_('Disable <resource>'), + ) + +An example handler in `take_action()`: + +.. code-block:: python + + # This leaves 'enabled' undefined if neither option is present + if parsed_args.enable: + kwargs['enabled'] = True + if parsed_args.disable: + kwargs['enabled'] = False + +Options with Choices +-------------------- + +Some options have a specific set of values (or choices) that are valid. +These choices may be validated by the CLI. If the underlying API is stable +and the list of choices are unlikely to change then the CLI may validate +the choices. Otherwise, the CLI must defer validation of the choices to +the API. If the option has a default choice then it must be documented. + +Having the CLI validate choices will be faster and may provide a better +error message for the user if an invalid choice is specified +(for example: ``argument --test: invalid choice: 'choice4' (choose from 'choice1', 'choice2', 'choice3')``). +The trade-off is that CLI changes are required in order to take advantage +of new choices. + +Implementation +~~~~~~~~~~~~~~ + +An example parser declaration: + +.. code-block:: python + + choice_option.add_argument( + '--test', + metavar='<test>', + choices=['choice1', 'choice2', 'choice3'], + help=_('Test type (choice1, choice2 or choice3)'), + ) + +Options with Multiple Values +---------------------------- + +Some options can be repeated to build a collection of values for a property. +Adding a value to the collection must be provided via the ``set`` action. +Removing a value from the collection must be provided via an ``unset`` action. +As a convenience, removing all values from the collection may be provided via a +``--no`` option on the ``set`` action and a ``--all`` option on ``unset`` +action. If both ``--no`` option and option are specified, the values specified +on the command would overwrite the collection property instead of appending on +the ``set`` action. The ``--all`` option must be part of a mutually exclusive +group with the related property option on the ``unset`` action, overwrite case +don't exist in ``unset`` action. + +An example behavior for ``set`` action: + +Append: + +.. code-block:: bash + + object set --example-property xxx + +Overwrite: + +.. code-block:: bash + + object set --no-example-property --example-property xxx + +The example below assumes a property that contains a list of unique values. +However, this example can also be applied to other collections using the +appropriate parser action and action implementation (e.g. a dict of key/value +pairs). Implementations will vary depending on how the REST API handles +adding/removing values to/from the collection and whether or not duplicate +values are allowed. + +Implementation +~~~~~~~~~~~~~~ + +An example parser declaration for `set` action: + +.. code-block:: python + + parser.add_argument( + '--no-example-property', + dest='no_example_property', + action='store_true', + help=_('Remove all example properties for this <resource> ' + '(specify both --no-example-property and --example-property' + ' to remove the current properties before setting' + ' new properties.)'), + ) + parser.add_argument( + '--example-property', + metavar='<example-property>', + dest='example_property', + action='append', + help=_('Example property for this <resource> ' + '(repeat option to set multiple properties)'), + ) + +Please make `--no-example-property` be shown in front of `--example-property` +in the help, like above, that help make users aware of the processing order. + +An example handler in `take_action()` for `set` action: + +.. code-block:: python + + if parsed_args.no_example_property and parsed_args.example_property: + kwargs['example_property'] = parsed_args.example_property + elif parsed_args.no_example_property: + kwargs['example_property'] = [] + elif parsed_args.example_property: + kwargs['example_property'] = \ + resource_example_property + parsed_args.example_property + +An example parser declaration for `unset` action: + +.. code-block:: python + + example_property_group = parser.add_mutually_exclusive_group() + example_property_group.add_argument( + '--example-property', + metavar='<example-property>', + dest='example_property', + action='append', + help=_('Example property for this <resource> ' + '(repeat option to remove multiple properties)'), + ) + example_property_group.add_argument( + '--all-example-property', + dest='all_example_property', + action='store_true', + help=_('Remove all example properties for this <resource>'), + ) + +An example handler in `take_action()` for `unset` action: + +.. code-block:: python + + if parsed_args.example_property: + kwargs['example_property'] = \ + list(set(resource_example_property) - \ + set(parsed_args.example_property)) + if parsed_args.all_example_property: + kwargs['example_property'] = [] + +Required Options +---------------- + +Some options have no default value and the API does not allow them to be +`None`, then these options are always required when users use the command +to which these options belong. + +Required options must be validated by the CLI to avoid omissions. The CLI +validation may provide an error message for the user if a required option +is not specified. +(for example: ``error: argument --test is required``) + +.. option:: --test + + Test option (required) + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this: + +.. code-block:: python + + parser.add_argument( + '--test', + metavar='<test>', + required=True, + help=_('Test option (required)'), + ) + +List Command Options +==================== + +Additional Fields +----------------- + +Most list commands only return a subset of the available fields by default. +Additional fields are available with the `--long` option. All list +commands should allow `--long` even if they return all fields by default. + +.. option:: --long + + List additional fields in output + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this: + +.. code-block:: python + + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + +Pagination +---------- + +There are many ways to do pagination, some OpenStack APIs support it, some +don't. OpenStackClient attempts to define a single common way to specify +pagination on the command line. + +.. option:: --marker <resource> + + Anchor for paging (name or ID) + +.. option:: --limit <num-resources> + + Limit number of <resource> returned (*integer*) + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this: + +.. code-block:: python + + parser.add_argument( + "--marker", + metavar="<resource>", + help="Anchor for paging (name or ID)", + ) + + parser.add_argument( + "--limit", + metavar="<num-resources>", + type=int, + help="Limit the number of <resource> returned", + ) diff --git a/doc/source/contributor/command-wrappers.rst b/doc/source/contributor/command-wrappers.rst new file mode 100644 index 00000000..2a5d9223 --- /dev/null +++ b/doc/source/contributor/command-wrappers.rst @@ -0,0 +1,52 @@ +====================== +Command Class Wrappers +====================== + +When we want to deprecate a command, policy says we need to alert the user. +We do this with a message logged at WARNING level before any command output +is emitted. + +OpenStackClient command classes are derived from the ``cliff`` classes. +Cliff uses ``setuptools`` entry points for dispatching the parsed command +to the respective handler classes. This lends itself to modifying the +command execution at run-time. + +The obvious approach to adding the deprecation message would be to just add +the message to the command class ``take_action()`` method directly. But then +the various deprecations are scattered throughout the code base. If we +instead wrap the deprecated command class with a new class we can put all of +the wrappers into a separate, dedicated module. This also lets us leave the +original class unmodified and puts all of the deprecation bits in one place. + +This is an example of a minimal wrapper around a command class that logs a +deprecation message as a warning to the user then calls the original class. + +* Subclass the deprecated command. + +* Set class attribute ``deprecated`` to ``True`` to signal cliff to not + emit help text for this command. + +* Log the deprecation message at WARNING level and refer to the replacement + for the deprecated command in the log warning message. + +* Change the entry point class in ``setup.cfg`` to point to the new class. + +Example Deprecation Class +------------------------- + +.. code-block:: python + + class ListFooOld(ListFoo): + """List resources""" + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning( + "%s is deprecated, use 'foobar list'", + getattr(self, 'cmd_name', 'this command'), + ) + return super(ListFooOld, self).take_action(parsed_args) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst new file mode 100644 index 00000000..2981ba4d --- /dev/null +++ b/doc/source/contributor/developing.rst @@ -0,0 +1,209 @@ +=============================== +Developing with OpenStackClient +=============================== + +Communication +------------- + +Meetings +======== +The OpenStackClient team meets regularly on every Thursday. For details +please refer to the `OpenStack IRC meetings`_ page. + +.. _`OpenStack IRC meetings`: http://eavesdrop.openstack.org/#OpenStackClient_Team_Meeting + +Testing +------- + +Tox prerequisites and installation +================================== + +Install the prerequisites for Tox: + +* On Ubuntu or Debian: + + .. code-block:: bash + + $ apt-get install gcc gettext python-dev libxml2-dev libxslt1-dev \ + zlib1g-dev + + You may need to use pip install for some packages. + + +* On RHEL or CentOS including Fedora: + + .. code-block:: bash + + $ yum install gcc python-devel libxml2-devel libxslt-devel + +* On openSUSE or SUSE linux Enterprise: + + .. code-block:: bash + + $ zypper install gcc python-devel libxml2-devel libxslt-devel + +Install python-tox: + +.. code-block:: bash + + $ pip install tox + +To run the full suite of tests maintained within OpenStackClient. + +.. code-block:: bash + + $ tox + +.. NOTE:: + + The first time you run ``tox``, it will take additional time to build + virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild + your virtualenv in a similar manner. + + +To run tests for one or more specific test environments(for example, the most common configuration of +Python 2.7 and PEP-8), list the environments with the ``-e`` option, separated by spaces: + +.. code-block:: bash + + $ tox -e py27,pep8 + +See ``tox.ini`` for the full list of available test environments. + +Running functional tests +======================== + +OpenStackClient also maintains a set of functional tests that are optimally +designed to be run against OpenStack's gate. Optionally, a developer may +choose to run these tests against any OpenStack deployment, however depending +on the services available, results vary. + +To run the entire suite of functional tests: + +.. code-block:: bash + + $ tox -e functional + +To run a specific functional test: + +.. code-block:: bash + + $ tox -e functional -- --regex functional.tests.compute.v2.test_server + +Running with PDB +================ + +Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since +the tests fail with a `BdbQuit` exception rather than stopping at the +breakpoint. + +To run with PDB breakpoints during testing, use the `debug` ``tox`` environment +rather than ``py27``. For example, passing a test name since you will normally +only want to run the test that hits your breakpoint: + +.. code-block:: bash + + $ tox -e debug openstackclient.tests.identity.v3.test_group + +For reference, the `debug`_ ``tox`` environment implements the instructions + +.. _`debug`: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests + + +Building the Documentation +-------------------------- + +The documentation is generated with Sphinx using the ``tox`` command. To +create HTML docs, run the commands: + +.. code-block:: bash + + $ tox -e docs + +The resultant HTML will be in the ``doc/build/html`` directory. + +Release Notes +------------- + +The release notes for a patch should be included in the patch. See the +`Project Team Guide`_ for more information on using reno in OpenStack. + +.. _`Project Team Guide`: http://docs.openstack.org/project-team-guide/release-management.html#managing-release-notes + +If any of the following applies to the patch, a release note is required: + +* The deployer needs to take an action when upgrading +* The plugin interface changes +* A new feature is implemented +* A command or option is removed +* Current behavior is changed +* A security bug is fixed + +Reno is used to generate release notes. Use the commands: + +.. code-block:: bash + + $ tox -e venv -- reno new <bug-,bp-,whatever> + +Then edit the sample file that was created and push it with your change. + +To run the commands and see results: + +.. code-block:: bash + + $ git commit # Commit the change because reno scans git log. + + $ tox -e releasenotes + +At last, look at the generated release notes files in ``releasenotes/build/html`` in your browser. + +Testing new code +---------------- + +If a developer wants to test new code (feature, command or option) that +they have written, OpenStackClient may be installed from source by running +the following commands in the base directory of the project: + +.. code-block:: bash + + $ python setup.py develop + +or + +.. code-block:: bash + + $ pip install -e . + +Standardize Import Format +========================= + +.. _`Import Order Guide`: http://docs.openstack.org/developer/hacking/#imports + +The import order shows below: + +* {{stdlib imports in human alphabetical order}} +* \n +* {{third-party lib imports in human alphabetical order}} +* \n +* {{project imports in human alphabetical order}} +* \n +* \n +* {{begin your code}} + +Example +~~~~~~~ + +.. code-block:: python + + import copy + import fixtures + import mock + import os + + from osc_lib.api import auth + from osc_lib import utils + import six + + from openstackclient import shell + from openstackclient.tests import utils + diff --git a/doc/source/contributor/humaninterfaceguide.rst b/doc/source/contributor/humaninterfaceguide.rst new file mode 100644 index 00000000..a7db3800 --- /dev/null +++ b/doc/source/contributor/humaninterfaceguide.rst @@ -0,0 +1,368 @@ +.. _hig: + +===================== +Human Interface Guide +===================== + +*Note: This page covers the OpenStackClient CLI only but looks familiar +because it was derived from the Horizon HIG.* + +Overview +======== + +What is a HIG? +The Human Interface Guidelines document was created for OpenStack developers +in order to direct the creation of new OpenStackClient command interfaces. + +Personas +======== + +Personas are archetypal users of the system. Keep these types of users in +mind when designing the interface. + +Alice the admin +--------------- + +Alice is an administrator who is responsible for maintaining the OpenStack +cloud installation. She has many years of experience with Linux systems +administration. + +Darren the deployer +------------------- + +Darren is responsible for doing the initial OpenStack deployment on the +host machines. + +Emile the end-user +------------------ + +Emile uses the cloud to do software development inside of the virtual +machines. She uses the command-line tools because she finds it quicker +than using the dashboard. + +Principles +========== + +The principles established in this section define the high-level priorities +to be used when designing and evaluating interactions for the OpenStack +command line interface. Principles are broad in scope and can be considered +the philosophical foundation for the OpenStack experience; while they may +not describe the tactical implementation of design, they should be used +when deciding between multiple courses of design. + +A significant theme for designing for the OpenStack experience concerns +focusing on common uses of the system rather than adding complexity to support +functionality that is rarely used. + +Consistency +----------- + +Consistency between OpenStack experiences will ensure that the command line +interface feels like a single experience instead of a jumble of disparate +products. Fractured experiences only serve to undermine user expectations +about how they should interact with the system, creating an unreliable user +experience. To avoid this, each interaction and visual representation within +the system must be used uniformly and predictably. The architecture and elements +detailed in this document will provide a strong foundation for establishing a +consistent experience. + +Example Review Criteria +~~~~~~~~~~~~~~~~~~~~~~~ + +* Do the command actions adhere to a consistent application of actions? +* Has a new type of command subject or output been introduced? +* Does the design use command elements (options and arguments) as defined? + (See Core Elements.) +* Can any newly proposed command elements (actions or subjects) be accomplished + with existing elements? + +* Does the design adhere to the structural model of the core experience? + (See Core Architecture.) +* Are any data objects displayed or manipulated in a way contradictory to how + they are handled elsewhere in the core experience? + +Simplicity +---------- + +To best support new users and create straight forward interactions, designs +should be as simple as possible. When crafting new commands, designs should +minimize the amount of noise present in output: large amounts of +nonessential data, overabundance of possible actions and so on. Designs should +focus on the intent of the command, requiring only the necessary components +and either removing superfluous elements or making +them accessible through optional arguments. An example of this principle occurs +in OpenStack's use of tables: only the most often used columns are shown by +default. Further data may be accessed through the output control options, +allowing users to specify the types of data that they find useful in their +day-to-day work. + +Example Review Criteria +~~~~~~~~~~~~~~~~~~~~~~~ + +* Can options be used to combine otherwise similar commands? + +* How many of the displayed elements are relevant to the majority of users? +* If multiple actions are required for the user to complete a task, is each + step required or can the process be more efficient? + +User-Centered Design +-------------------- + +Commands should be design based on how a user will interact with the system +and not how the system's backend is organized. While database structures and +APIs may define what is possible, they often do not define good user +experience; consider user goals and the way in which users will want to +interact with their data, then design for these work flows and mold the +interface to the user, not the user to the interface. + +Commands should be discoverable via the interface itself. + +To determine a list of available commands, use the :code:`-h` or +:code:`--help` options: + +.. code-block:: bash + + $ openstack --help + +For help with an individual command, use the :code:`help` command: + +.. code-block:: bash + + $ openstack help server create + +Example Review Criteria +~~~~~~~~~~~~~~~~~~~~~~~ + +* How quickly can a user figure out how to accomplish a given task? +* Has content been grouped and ordered according to usage relationships? +* Do work flows support user goals or add complexity? + +Transparency +------------ + +Make sure users understand the current state of their infrastructure and +interactions. For example, users should be able to access information about +the state of each machine/virtual machine easily, without having to actively +seek out this information. Whenever the user initiates an action, make sure +a confirmation is displayed[1] to show that an input has been received. Upon +completion of a process, make sure the user is informed. Ensure that the user +never questions the state of their environment. + +[1] This goes against the common UNIX philosophy of only reporting error +conditions and output that is specifically requested. + +Example Review Criteria +~~~~~~~~~~~~~~~~~~~~~~~ + +* Does the user receive feedback when initiating a process? +* When a process is completed? +* Does the user have quick access to the state of their infrastructure? + + +Architecture +============ + +Command Structure +----------------- + +OpenStackClient has a consistent and predictable format for all of its commands. + +* The top level command name is :code:`openstack` +* Sub-commands take the form: + +.. code-block:: bash + + openstack [<global-options>] <object-1> <action> [<object-2>] [<command-arguments>] + +Subcommands shall have three distinct parts to its commands (in order that they appear): + +* global options +* command object(s) and action +* command options and arguments + +Output formats: + +* user-friendly tables with headers, etc +* machine-parsable delimited + +Global Options +~~~~~~~~~~~~~~ + +Global options are global in the sense that they apply to every command +invocation regardless of action to be performed. They include authentication +credentials and API version selection. Most global options have a corresponding +environment variable that may also be used to set the value. If both are present, +the command-line option takes priority. The environment variable names are derived +from the option name by dropping the leading dashes ('--'), converting each embedded +dash ('-') to an underscore ('_'), and converting to upper case. + +* Global options shall always have a long option name, certain common options may + also have short names. Short names should be reserved for global options to limit + the potential for duplication and multiple meanings between commands given the + limited set of available short names. +* All long options names shall begin with two dashes ('--') and use a single dash + ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not + be used in option names. +* Authentication options conform to the common CLI authentication guidelines in + :ref:`authentication`. + +For example, :code:`--os-username` can be set from the environment via +:code:`OS_USERNAME`. + +--help +++++++ + +The standard :code:`--help` global option displays the documentation for invoking +the program and a list of the available commands on standard output. All other +options and commands are ignored when this is present. The traditional short +form help option (:code:`-h`) is also available. + +--version ++++++++++ + +The standard :code:`--version` option displays the name and version on standard +output. All other options and commands are ignored when this is present. + +Command Object(s) and Action +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Commands consist of an object described by one or more words followed by an action. Commands that require two objects have the primary object ahead of the action and the secondary object after the action. Any positional arguments identifying the objects shall appear in the same order as the objects. In badly formed English it is expressed as "(Take) object1 (and perform) action (using) object2 (to it)." + + <object-1> <action> [<object-2>] + +Examples: + +* :code:`group add user <group> <user>` +* :code:`volume type list` # Note that :code:`volume type` is a two-word + single object + +The :code:`help` command is unique as it appears in front of a normal command +and displays the help text for that command rather than execute it. + +Object names are always specified in command in their singular form. This is +contrary to natural language use. + +Command Arguments and Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each command may have its own set of options distinct from the global options. +They follow the same style as the global options and always appear between +the command and any positional arguments the command requires. + +Command options shall only have long names. The small range of available +short names makes it hard for a single short option name to have a consistent +meaning across multiple commands. + +Option Forms +++++++++++++ + +* **boolean**: boolean options shall use a form of :code:`--<true>|--<false>` + (preferred) or :code:`--<option>|--no-<option>`. For example, the + :code:`enabled` state of a project is set with :code:`--enable|--disable`. + +Command Output +-------------- + +The default command output is pretty-printed using the Python +:code:`prettytable` module. + +Machine-parsable output format may be specified with the :code:`--format` +option to :code:`list` and :code:`show` commands. :code:`list` commands +have an option (:code:`--format csv`) for CSV output and :code:`show` commands +have an option (:code:`--format shell`) for the shell variable assignment +syntax of :code:`var="value"`. In both cases, all data fields are quoted with `"` + +Help Commands +------------- + +The help system is considered separately due to its special status +among the commands. Rather than performing tasks against a system, it +provides information about the commands available to perform those +tasks. The format of the :code:`help` command therefore varies from the +form for other commands in that the :code:`help` command appears in front +of the first object in the command. + +The options :code:`--help` and :code:`-h` display the global options and a +list of the supported commands. Note that the commands shown depend on the API +versions that are in effect; i.e. if :code:`--os-identity-api-version=3` is +present Identity API v3 commands are shown. + +Examples +======== + +The following examples depict common command and output formats expected to be produces by the OpenStack client. + +Authentication +-------------- + +Using global options: + +.. code-block:: bash + + $ openstack --os-tenant-name ExampleCo --os-username demo --os-password secret --os-auth-url http://localhost:5000:/v2.0 server show appweb01 + +------------------------+-----------------------------------------------------+ + | Property | Value | + +------------------------+-----------------------------------------------------+ + | OS-DCF:diskConfig | MANUAL | + | OS-EXT-STS:power_state | 1 | + | flavor | m1.small | + | id | dcbc2185-ba17-4f81-95a9-c3fae9b2b042 | + | image | Ubuntu 12.04 (754c231e-ade2-458c-9f91-c8df107ff7ef) | + | keyname | demo-key | + | name | appweb01 | + | private_address | 10.4.128.13 | + | status | ACTIVE | + | user | demo | + +------------------------+-----------------------------------------------------+ + +Using environment variables: + +.. code-block:: bash + + $ export OS_TENANT_NAME=ExampleCo + $ export OS_USERNAME=demo + $ export OS_PASSWORD=secret + $ export OS_AUTH_URL=http://localhost:5000:/v2.0 + $ openstack server show appweb01 + +------------------------+-----------------------------------------------------+ + | Property | Value | + +------------------------+-----------------------------------------------------+ + | OS-DCF:diskConfig | MANUAL | + | OS-EXT-STS:power_state | 1 | + | flavor | m1.small | + | id | dcbc2185-ba17-4f81-95a9-c3fae9b2b042 | + | image | Ubuntu 12.04 (754c231e-ade2-458c-9f91-c8df107ff7ef) | + | keyname | demo-key | + | name | appweb01 | + | private_address | 10.4.128.13 | + | status | ACTIVE | + | user | demo | + +------------------------+-----------------------------------------------------+ + +Machine Output Format +--------------------- + +Using the csv output format with a list command: + +.. code-block:: bash + + $ openstack server list --format csv + "ID","Name","Status","Private_Address" + "ead97d84-6988-47fc-9637-3564fc36bc4b","appweb01","ACTIVE","10.4.128.13" + +Using the show command options of shell output format and adding a prefix of +:code:`my_` to avoid collisions with existing environment variables: + +.. code-block:: bash + + $ openstack server show --format shell --prefix my_ appweb01 + my_OS-DCF:diskConfig="MANUAL" + my_OS-EXT-STS:power_state="1" + my_flavor="m1.small" + my_id="dcbc2185-ba17-4f81-95a9-c3fae9b2b042" + my_image="Ubuntu 12.04 (754c231e-ade2-458c-9f91-c8df107ff7ef)" + my_keyname="demo-key" + my_name="appweb01" + my_private_address="10.4.128.13" + my_status="ACTIVE" + my_user="demo" diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst new file mode 100644 index 00000000..4438f2ad --- /dev/null +++ b/doc/source/contributor/index.rst @@ -0,0 +1,16 @@ +=========================== + Contributor Documentation +=========================== + +.. toctree:: + :maxdepth: 1 + + developing + command-beta + command-options + command-wrappers + command-errors + command-logs + specs/commands + plugins + humaninterfaceguide diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst new file mode 100644 index 00000000..13f5d495 --- /dev/null +++ b/doc/source/contributor/plugins.rst @@ -0,0 +1,245 @@ +.. _plugins: + +======= +Plugins +======= + +The OpenStackClient plugin system is designed so that the plugin need only be +properly installed for OSC to find and use it. It utilizes the +``setuptools`` entry points mechanism to advertise to OSC the +plugin module and supported commands. + +Adoption +======== + +OpenStackClient promises to provide first class support for the following +OpenStack services: Compute, Identity, Image, Object Storage, Block Storage +and Network (core objects). These services are considered essential +to any OpenStack deployment. + +Other OpenStack services, such as Orchestration or Telemetry may create an +OpenStackClient plugin. The source code will not be hosted by +OpenStackClient. + +The following is a list of projects that are an OpenStackClient plugin. + +- aodhclient +- gnocchiclient +- python-barbicanclient +- python-congressclient +- python-designateclient +- python-heatclient +- python-ironicclient +- python-ironic-inspector-client +- python-karborclient +- python-mistralclient +- python-muranoclient +- python-neutronclient\*\*\* +- python-saharaclient +- python-searchlightclient +- python-senlinclient +- python-tripleoclient\*\* +- python-troveclient +- python-watcherclient +- python-zaqarclient + +\*\* Note that some clients are not listed in global-requirements. + +\*\*\* Project contains advanced network services. + +The following is a list of projects that are not an OpenStackClient plugin. + +- python-magnumclient +- python-ceilometerclient +- python-solumclient + +Implementation +============== + +Client module +------------- + +Plugins are discovered by enumerating the entry points +found under :py:mod:`openstack.cli.extension` and initializing the specified +client module. + +.. code-block:: ini + + [entry_points] + openstack.cli.extension = + oscplugin = oscplugin.client + +The client module must define the following top-level variables: + +* ``API_NAME`` - A string containing the plugin API name; this is + the name of the entry point declaring the plugin client module + (``oscplugin = ...`` in the example above) and the group name for + the plugin commands (``openstack.oscplugin.v1 =`` in the example below). + OSC reserves the following API names: ``compute``, ``identity``, + ``image``, ``network``, ``object_store`` and ``volume``. +* ``API_VERSION_OPTION`` (optional) - If set, the name of the API + version attribute; this must be a valid Python identifier and + match the destination set in ``build_option_parser()``. +* ``API_VERSIONS`` - A dict mapping a version string to the client class + +The client module must implement the following interface functions: + +* ``build_option_parser(parser)`` - Hook to add global options to the parser +* ``make_client(instance)`` - Hook to create the client object + +OSC enumerates the plugin commands from the entry points in the usual manner +defined for the API version: + +.. code-block:: ini + + openstack.oscplugin.v1 = + plugin_list = oscplugin.v1.plugin:ListPlugin + plugin_show = oscplugin.v1.plugin:ShowPlugin + +Note that OSC defines the group name as :py:mod:`openstack.<api-name>.v<version>` +so the version should not contain the leading 'v' character. + +.. code-block:: python + + from osc_lib import utils + + + DEFAULT_API_VERSION = '1' + + # Required by the OSC plugin interface + API_NAME = 'oscplugin' + API_VERSION_OPTION = 'os_oscplugin_api_version' + API_VERSIONS = { + '1': 'oscplugin.v1.client.Client', + } + + # Required by the OSC plugin interface + def make_client(instance): + """Returns a client to the ClientManager + + Called to instantiate the requested client version. instance has + any available auth info that may be required to prepare the client. + + :param ClientManager instance: The ClientManager that owns the new client + """ + plugin_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + + client = plugin_client() + return client + + # Required by the OSC plugin interface + def build_option_parser(parser): + """Hook to add global options + + Called from openstackclient.shell.OpenStackShell.__init__() + after the builtin parser has been initialized. This is + where a plugin can add global options such as an API version setting. + + :param argparse.ArgumentParser parser: The parser object that has been + initialized by OpenStackShell. + """ + parser.add_argument( + '--os-oscplugin-api-version', + metavar='<oscplugin-api-version>', + help='OSC Plugin API version, default=' + + DEFAULT_API_VERSION + + ' (Env: OS_OSCPLUGIN_API_VERSION)') + return parser + +Client usage of OSC interfaces +------------------------------ + +OSC provides the following interfaces that may be used to implement +the plugin commands: + +.. code-block:: python + + # osc-lib interfaces available to plugins: + from osc_lib.cli import parseractions + from osc_lib.command import command + from osc_lib import exceptions + from osc_lib import logs + from osc_lib import utils + + + class DeleteMypluginobject(command.Command): + """Delete mypluginobject""" + + ... + + def take_action(self, parsed_args): + # Client manager interfaces are available to plugins. + # This includes the OSC clients created. + client_manager = self.app.client_manager + + ... + + return + +OSC provides the following interfaces that may be used to implement +unit tests for the plugin commands: + +.. code-block:: python + + # OSC unit test interfaces available to plugins: + from openstackclient.tests import fakes + from openstackclient.tests import utils + + ... + +Requirements +------------ + +OSC should be included in the plugin's ``test-requirements.txt`` if +the plugin can be installed as a library with the CLI being an +optional feature (available when OSC is also installed). + +OSC should not appear in ``requirements.txt`` unless the plugin project +wants OSC and all of its dependencies installed with it. This is +specifically not a good idea for plugins that are also libraries +installed with OpenStack services. + +.. code-block:: ini + + python-openstackclient>=X.Y.Z # Apache-2.0 + +Checklist for adding new OpenStack plugins +========================================== + +Creating the initial plugin described above is the first step. There are a few +more steps needed to fully integrate the client with openstackclient. + +Add the command checker to your CI +---------------------------------- + +#. Modify the section of ``zuul/layout.yaml`` related to your repository to + add ``osc-plugin-jobs`` to the list of job templates for your project. + This job checks that to see if any new commands are: duplicated, missing + entry points, or have overlap; across all openstackclient plugins. + +#. Update ``jenkins/scripts/check-osc-plugins.sh`` to include your new + library to be installed from source. This is essential in running the + previously mentioned check job. Simply add + ``install_from_source python-fooclient`` to the block of code where all + other clients are installed. + +Changes to python-openstackclient +--------------------------------- + +#. In ``doc/source/plugins.rst``, update the `Adoption` section to reflect the + status of the project. + +#. Update ``doc/source/commands.rst`` to include objects that are defined by + fooclient's new plugin. + +#. Update ``doc/source/plugin-commands.rst`` to include the entry point defined + in fooclient. We use `sphinxext`_ to automatically document commands that + are used. + +#. Update ``test-requirements.txt`` to include fooclient. This is necessary + to auto-document the commands in the previous step. + +.. _sphinxext: http://docs.openstack.org/developer/stevedore/sphinxext.html diff --git a/doc/source/contributor/specs/command-objects/example.rst b/doc/source/contributor/specs/command-objects/example.rst new file mode 100644 index 00000000..fa559433 --- /dev/null +++ b/doc/source/contributor/specs/command-objects/example.rst @@ -0,0 +1,86 @@ +======= +example +======= + +This is a specification for the ``example`` command object. It is not intended +to be a complete template for new commands since other actions, options +and/or arguments may be used. You can include general specification information +before the commands below. This information could include links to related material +or descriptions of similar commands. + +[example API name] [example API version] + +example create +-------------- + +Create new example + +.. program:: example create +.. code:: bash + + openstack example create + <name> + +.. describe:: <name> + + New example name + +example delete +-------------- + +Delete example(s) + +.. program:: example delete +.. code:: bash + + openstack example delete + <example> [<example> ...] + +.. describe:: <example> + + Example(s) to delete (name or ID) + +example list +------------ + +List examples + +.. program:: example list +.. code:: bash + + openstack example list + +example set +----------- + +Set example properties + +.. program:: example set +.. code:: bash + + openstack example set + [--name <new-name>] + <example> + +.. option:: --name <new-name> + + New example name + +.. describe:: <example> + + Example to modify (name or ID) + +example show +------------ + +Display example details + +.. program:: example show +.. code:: bash + + openstack example show + <example> + +.. describe:: <example> + + Example to display (name or ID) diff --git a/doc/source/contributor/specs/commands.rst b/doc/source/contributor/specs/commands.rst new file mode 100644 index 00000000..f9d757e7 --- /dev/null +++ b/doc/source/contributor/specs/commands.rst @@ -0,0 +1,44 @@ +============= +Command Specs +============= + +Specifications for new commands, objects and actions are listed below. +These specifications have not been implemented. See +:ref:`command-list` for implemented commands and +:ref:`command-structure` for implemented objects and actions. + +It is optional to propose a specifications patch for new commands, +objects and actions here before submitting the implementation. Once your +specifications patch merges then you may proceed with the implementation. +Your implementation patches should move applicable portions of the +specifications patch to the official :ref:`command-list` +and :ref:`command-structure` documentation. + +Objects Specs +------------- + +Add specifications for new objects based on the ``example`` object. + +Actions Specs +------------- + +Add specifications for new actions based on the ``example`` action. + +.. toctree:: + :maxdepth: 1 + + network-topology + +Commands Specs +-------------- + +Add specifications for new commands based on the commands for the +``example`` object. The ``example`` commands are not intended to +be a complete template for new commands since other actions, options +and/or arguments may be used. + +.. toctree:: + :glob: + :maxdepth: 2 + + command-objects/* diff --git a/doc/source/contributor/specs/network-topology.rst b/doc/source/contributor/specs/network-topology.rst new file mode 100755 index 00000000..6789ee97 --- /dev/null +++ b/doc/source/contributor/specs/network-topology.rst @@ -0,0 +1,44 @@ +================ +network topology +================ + +A **network topology** shows a topological graph about +devices which connect to the specific network. Also, it +will return availability information for each individual +device within the network as well. One other thing to note +is that it is the intention for OSC to collect data from +existing REST APIs + +Network v2 + +network topology list +--------------------- + +List network topologies + +.. program:: network topology list +.. code:: bash + + openstack network topology list + [--project <project>] + +.. option:: --project <project> + + List network topologies for given project + (name or ID) + +network topology show +--------------------- + +Show network topology details + +.. program:: network topology show +.. code:: bash + + openstack network topology show + <network> + +.. _network_topology_show-network: +.. describe:: <network> + + Show network topology for a specific network (name or ID) |
