Skip to content

Commit 7608123

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Add doc describing how to handle API errors"
2 parents 586a038 + 3d6b072 commit 7608123

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

doc/source/command-errors.rst

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
==============
2+
Command Errors
3+
==============
4+
5+
Handling errors in OpenStackClient commands is fairly straightforward. An
6+
exception is thrown and handled by the application-level caller.
7+
8+
Note: There are many cases that need to be filled out here. The initial
9+
version of this document considers the general command error handling as well
10+
as the specific case of commands that make multiple REST API calls and how to
11+
handle when one or more of those calls fails.
12+
13+
General Command Errors
14+
======================
15+
16+
The general pattern for handling OpenStackClient command-level errors is to
17+
raise a CommandError exception with an appropriate message. This should include
18+
conditions arising from arguments that are not valid/allowed (that are not otherwise
19+
enforced by ``argparse``) as well as errors arising from external conditions.
20+
21+
External Errors
22+
---------------
23+
24+
External errors are a result of things outside OpenStackClient not being as
25+
expected.
26+
27+
Example
28+
~~~~~~~
29+
30+
This example is taken from ``keypair create`` where the ``--public-key`` option
31+
specifies a file containing the public key to upload. If the file is not found,
32+
the IOError exception is trapped and a more specific CommandError exception is
33+
raised that includes the name of the file that was attempted to be opened.
34+
35+
.. code-block:: python
36+
37+
class CreateKeypair(command.ShowOne):
38+
"""Create new public key"""
39+
40+
## ...
41+
42+
def take_action(self, parsed_args):
43+
compute_client = self.app.client_manager.compute
44+
45+
public_key = parsed_args.public_key
46+
if public_key:
47+
try:
48+
with io.open(
49+
os.path.expanduser(parsed_args.public_key),
50+
"rb"
51+
) as p:
52+
public_key = p.read()
53+
except IOError as e:
54+
msg = "Key file %s not found: %s"
55+
raise exceptions.CommandError(
56+
msg % (parsed_args.public_key, e),
57+
)
58+
59+
keypair = compute_client.keypairs.create(
60+
parsed_args.name,
61+
public_key=public_key,
62+
)
63+
64+
## ...
65+
66+
REST API Errors
67+
===============
68+
69+
Most commands make a single REST API call via the supporting client library
70+
or SDK. Errors based on HTML return codes are usually handled well by default,
71+
but in some cases more specific or user-friendly messages need to be logged.
72+
Trapping the exception and raising a CommandError exception with a useful
73+
message is the correct approach.
74+
75+
Multiple REST API Calls
76+
-----------------------
77+
78+
Some CLI commands make multiple calls to library APIs and thus REST APIs.
79+
Most of the time these are ``create`` or ``set`` commands that expect to add or
80+
change a resource on the server. When one of these calls fails, the behaviour
81+
of the remainder of the command handler is defined as such:
82+
83+
* Whenever possible, all API calls will be made. This may not be possible for
84+
specific commands where the subsequent calls are dependent on the results of
85+
an earlier call.
86+
87+
* Any failure of an API call will be logged for the user
88+
89+
* A failure of any API call results in a non-zero exit code
90+
91+
* In the cases of failures in a ``create`` command a follow-up mode needs to
92+
be present that allows the user to attempt to complete the call, or cleanly
93+
remove the partially-created resource and re-try.
94+
95+
The desired behaviour is for commands to appear to the user as idempotent
96+
whenever possible, i.e. a partial failure in a ``set`` command can be safely
97+
retried without harm. ``create`` commands are a harder problem and may need
98+
to be handled by having the proper options in a set command available to allow
99+
recovery in the case where the primary resource has been created but the
100+
subsequent calls did not complete.
101+
102+
Example
103+
~~~~~~~
104+
105+
This example is taken from the ``volume snapshot set`` command where ``--property``
106+
arguments are set using the volume manager's ``set_metadata()`` method,
107+
``--state`` arguments are set using the ``reset_state()`` method, and the
108+
remaining arguments are set using the ``update()`` method.
109+
110+
.. code-block:: python
111+
112+
class SetSnapshot(command.Command):
113+
"""Set snapshot properties"""
114+
115+
## ...
116+
117+
def take_action(self, parsed_args):
118+
volume_client = self.app.client_manager.volume
119+
snapshot = utils.find_resource(
120+
volume_client.volume_snapshots,
121+
parsed_args.snapshot,
122+
)
123+
124+
kwargs = {}
125+
if parsed_args.name:
126+
kwargs['name'] = parsed_args.name
127+
if parsed_args.description:
128+
kwargs['description'] = parsed_args.description
129+
130+
result = 0
131+
if parsed_args.property:
132+
try:
133+
volume_client.volume_snapshots.set_metadata(
134+
snapshot.id,
135+
parsed_args.property,
136+
)
137+
except SomeException: # Need to define the exceptions to catch here
138+
self.app.log.error("Property set failed")
139+
result += 1
140+
141+
if parsed_args.state:
142+
try:
143+
volume_client.volume_snapshots.reset_state(
144+
snapshot.id,
145+
parsed_args.state,
146+
)
147+
except SomeException: # Need to define the exceptions to catch here
148+
self.app.log.error("State set failed")
149+
result += 1
150+
151+
try:
152+
volume_client.volume_snapshots.update(
153+
snapshot.id,
154+
**kwargs
155+
)
156+
except SomeException: # Need to define the exceptions to catch here
157+
self.app.log.error("Update failed")
158+
result += 1
159+
160+
# NOTE(dtroyer): We need to signal the error, and a non-zero return code,
161+
# without aborting prematurely
162+
if result > 0:
163+
raise SomeNonFatalException

doc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Developer Documentation
4949
developing
5050
command-options
5151
command-wrappers
52+
command-errors
5253
specs/commands
5354

5455
Project Goals

0 commit comments

Comments
 (0)