Errors — python-octaviaclient — branch stable/2026.2
Branch
stable/2026.2@b37975ec8f2d(tag3.15.0+ 2 commits) — repository https://opendev.org/openstack/python-octaviaclient — analysed on 2026-11-01
[observed] means reproduced against the local stub (scenarios in [_rewrite/defect-repro_stable-2026.2.json](_rewrite/defect-repro_stable-2026.2.json)). A stub shows how the client reacts to an answer, not whether a real service sends that answer.
1. Exit codes
| Exit | When | Source |
|---|---|---|
| 0 | the command returned normally | cliff:cliff/command.py:196 |
| 1 | any exception raised by a command; the text goes to stderr through the logger | cliff:cliff/app.py:435, cliff:cliff/app.py:462-467 |
| 2 | argparse rejected the arguments, or the command does not exist | cliff:cliff/app.py:428 |
**The command.Command return value is not a problem here**: cliff treats a returned value as the exit code (cliff:cliff/command.py:196), but none of the 38 command.Command classes returns one. This was checked by an AST walk, and all of them exit 0 on the typed stub [observed].
2. HTTP errors: correct_return_codes
All 73 API methods go through this decorator (octaviaclient/api/v2/octavia.py:26-62). keystoneauth raises an HttpError for any status of 400 or more; it carries response and request_id (keystoneauth:keystoneauth1/exceptions/http.py:81-82).
| Situation | Exception reaching the user | Text | Source |
|---|---|---|---|
exception with response, JSON body with faultstring | OctaviaClientException | <faultstring> (HTTP <code>) (Request-ID: <id>) | octaviaclient/api/v2/octavia.py:41-46, octaviaclient/api/exceptions.py:24-27 |
same, no faultstring; status in the table | same | Forbidden (HTTP 403) (Request-ID: None) [observed] | octaviaclient/api/v2/octavia.py:27-30 |
| same, status not in the table (500, 502…) | same | Unknown Error (HTTP 500) (Request-ID: None) [observed] | octaviaclient/api/v2/octavia.py:37 |
| body is not JSON, or JSON that is not an object | same | status table text [observed for a list body and for HTML] | octaviaclient/api/v2/octavia.py:47-48 |
osc-lib ClientException with code != http_status | same | the code value as message | octaviaclient/api/v2/octavia.py:49-53 |
anything else, including osc-lib NotFound(404, …) | unchanged | <value> not found (HTTP 404) from find | octaviaclient/api/v2/octavia.py:54-55, osc-lib:osc_lib/api/api.py:397-399 |
Status table: 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not found, 409 Conflict, 413 Over Limit, 501 Not Implemented, 503 Service Unavailable. The request ID comes from the service's x-openstack-request-id header: denied (HTTP 403) (Request-ID: req-1234) [observed].
Consequence of the first row: the body {"faultcode", "faultstring", "debuginfo"} is what puts the service's text in front of the user. Octavia sends it (octavia:octavia/tests/functional/api/v2/test_load_balancer.py:139). A service using another shape shows canned text only.
**Status on GET of one resource**: find turns 404 and 400 into osc-lib NotFound (osc-lib:osc_lib/api/api.py:411-425), which triggers the show fallback [observed]. A 403 becomes denied (HTTP 403)… and stops there [observed].
3. Messages raised by the client
| Message (template) | Exception | Situation | Raised at | Observed |
|---|---|---|---|---|
Unable to locate {name} in {resource_name} | CommandError | lookup by ID and by name found nothing | octaviaclient/osc/v2/utils.py:102-103, octaviaclient/osc/v2/utils.py:196-197 | yes |
{n} {resource_name} found with name or ID of {id}. Please try again with UUID | CommandError | several resources share the name | octaviaclient/osc/v2/utils.py:138-140 | yes |
Additional VIPs must include a subnet-id. | CommandError | --additional-vip without subnet-id | octaviaclient/osc/v2/utils.py:215-216 | yes |
text of ipaddress, for example 'abc' does not appear to be an IPv4 or IPv6 address | CommandError | bad ip-address in --additional-vip | octaviaclient/osc/v2/utils.py:222-225 | yes |
Missing required argument: Requires one of --vip-subnet-id, --vip-network-id or --vip-port-id | CommandError | loadbalancer create with no VIP option | octaviaclient/osc/v2/load_balancer.py:41-43 | yes |
Argument error: --vip-port-id can not be used with --vip-network-id | CommandError | both options | octaviaclient/osc/v2/load_balancer.py:45-47 | yes, after both lookups |
Missing argument: --redirect-pool, --redirect-url, --redirect-prefix | CommandError | --action without its target, on create and set | octaviaclient/osc/v2/validate.py:25-33 | yes for the first |
Missing argument: --type {type_name} requires --key <key> | CommandError | COOKIE or HEADER without --key, on create and set | octaviaclient/osc/v2/validate.py:40-43 | yes |
Invalid input for field/attribute '{name}', Value: '{port}'. Value must be between 1 and 65535. | InvalidValue | --protocol-port out of range on listener and member create | octaviaclient/osc/v2/validate.py:51-56 | yes |
Invalid input for field/attribute 'weight', Value: '{weight}'. Value must be between 0 and 256. | InvalidValue | --weight out of range | octaviaclient/osc/v2/validate.py:83-88 | yes |
Argument hsts_max_age is required when using hsts_preload or hsts_include_subdomains arguments. | InvalidValue | listener create only | octaviaclient/osc/v2/validate.py:67-69 | yes |
Missing required argument. Requires at least one of: --healthmonitor, --listener, --loadbalancer, --member, --pool, --l7policy, --l7rule | CommandError | quota set with no limit | octaviaclient/osc/v2/quota.py:108-110 | yes |
The resource did not successfully reach ACTIVE status. (HTTP n/a) (Request-ID: None) | OctaviaClientException | --wait read error | octaviaclient/osc/v2/utils.py:796-798 | yes |
The resource could not be successfully deleted. (HTTP n/a) (Request-ID: None) | OctaviaClientException | delete --wait read error, or 300 s passed | octaviaclient/osc/v2/utils.py:816-818 | yes for error |
<value> not found (HTTP 404) | osc-lib NotFound | direct GET answered 404 or 400, with no fallback (amphora show, stats) | osc-lib:osc_lib/api/api.py:397-399 | yes |
No project with a name or ID of 'x' exists. | CommandError | project lookup found nothing | osc-lib:osc_lib/utils/__init__.py:290 | yes |
public endpoint for load-balancer service in RegionOne region not found | keystoneauth | no such catalog entry | octaviaclient/osc/plugin.py:32-36 | yes |
4. How a failure reaches the user
flowchart TD
a[HTTP status 400 or more] --> b[keystoneauth HttpError]
b --> c{find on 404 or 400?}
c -->|yes| d[osc-lib NotFound, show falls back on lookup]
c -->|no| e[correct_return_codes: OctaviaClientException]
f[local validation] --> g[CommandError or InvalidValue]
h[missing key, bad body, bad input] --> i[KeyError, TypeError, AttributeError, ValueError]
d --> j[cliff logs the text on stderr, exit 1]
e --> j
g --> j
i --> jThere is no asynchronous error beyond --wait, and no log message of the plugin's own. The package never uses its LOG (octaviaclient/osc/plugin.py:19).
5. Defects
Reproductions are in _rewrite/defect-repro_stable-2026.2.json, by scenario name. The stub chooses the answer, so "reproduced" means the client behaves this way on that answer. Whether a real service sends it is stated row by row.
| # | Defect | What the user sees | Code | Evidence |
|---|---|---|---|---|
| 1 | The filter is dropped from page 2 onwards. params is replaced by the next link's query | extra, unfiltered rows, exit 0 | octaviaclient/api/v2/octavia.py:97-98; Octavia's link holds only limit, sort, marker: octavia:octavia/api/common/pagination.py:151-172 | pagination_filter_lost: ?name=x, then ?limit=1&marker=page1 [observed client]. Needs a full first page; the default is 1,000 (octavia:octavia/common/constants.py:755). Not run against Octavia |
| 2 | **13 commands need a network catalog entry even with no VIP option**: loadbalancer create, list, show (by name), set, delete, failover, stats show, status show, and member create, list, show, set, delete | Service 'network' is disabled because its configuration could not be loaded…, no request to Octavia | maps read client_manager.sdk_connection.network.* on every call: octaviaclient/osc/v2/utils.py:273, octaviaclient/osc/v2/utils.py:456 | field no_network_in_catalog: 13 fail, 69 exit 0 [observed] |
| 3 | **--wait for ACTIVE never gives up** | hangs until interrupted | octaviaclient/osc/v2/utils.py:789-794, osc-lib:osc_lib/utils/__init__.py:805-818 | wait_active_pending_forever: 6 polls in 12 s, then killed by the probe's own alarm [observed]. An unbounded loop was read in the code, not waited for |
| 4 | Delete timeout and error share a message: after 300 s, or on error | The resource could not be successfully deleted. | octaviaclient/osc/v2/utils.py:815-818, osc-lib:osc_lib/utils/__init__.py:727 | wait_delete_error [observed]; the 300 s branch was read only |
| 5 | **A 200 with a non-JSON body on a single GET**: requests.JSONDecodeError has response = None (requests:requests/exceptions.py:30), and the decorator reads e.response.status_code | 'NoneType' object has no attribute 'status_code' | octaviaclient/api/v2/octavia.py:41-42 | show_html_200 [observed]. A proxy error page is a realistic source [inferred] |
| 6 | A non-JSON body on list or create: BaseAPI returns the Response, the caller indexes it | 'Response' object is not subscriptable. The create was already sent | osc-lib:osc_lib/api/api.py:180-183, octaviaclient/api/v2/octavia.py:92, octaviaclient/osc/v2/load_balancer.py:178 | list_html_200, create_empty_201 [observed]. set ignores the body and exits 0: set_html_200 |
| 7 | **{} on a single GET** | list index out of range | osc-lib:osc_lib/api/api.py:409 | show_uuid_empty_object [observed] |
| 8 | A wrapper that is not the first key | 'int' object has no attribute 'get' with {"aaa":1,"loadbalancer":{…}} | osc-lib:osc_lib/api/api.py:409-410 | show_wrapper_not_first [observed] |
| 9 | A missing key gives a bare key name | 'loadbalancers', 'stats', 'tags', and so on | octaviaclient/api/v2/octavia.py:92, octaviaclient/osc/v2/load_balancer.py:592, octaviaclient/osc/v2/utils.py:827, octaviaclient/osc/v2/utils.py:839 | list_empty_object, stats_without_key, set_tag_no_tags_field, and the whole 200 probe [observed] |
| 10 | **--wait on a response without the status field** | 'NoneType' object has no attribute 'lower', since _Munch answers None for any attribute | octaviaclient/osc/v2/utils.py:784-785, osc-lib:osc_lib/utils/__init__.py:807 | wait_active_no_status_field [observed] |
| 11 | **create --wait fails after a successful create** when the parent list is empty | list index out of range, exit 1, listener exists. A retrying script will conflict or duplicate | octaviaclient/osc/v2/listener.py:253, octaviaclient/osc/v2/pool.py:176, octaviaclient/osc/v2/health_monitor.py:159 | create_wait_no_parent [observed] |
| 12 | Bad user input gives a raw Python message | not enough values to unpack (expected 2, got 1), too many values to unpack (expected 2), invalid literal for int() with base 10: 'abc'. A header value containing = cannot be expressed | octaviaclient/osc/v2/utils.py:762, octaviaclient/osc/v2/utils.py:234, octaviaclient/osc/v2/utils.py:566 | kv_no_equals_insert_headers, kv_two_equals_insert_headers, kv_no_equals_session_persistence, additional_vip_no_equals, hm_delay_not_int [observed] |
| 13 | The monitor port check is dead: check_member_attrs reads member_port, while the map produces monitor_port. The unit test passes member_port, so it stays green | --monitor-port 70000 is sent to the service | octaviaclient/osc/v2/validate.py:77-78, octaviaclient/osc/v2/utils.py:458, octaviaclient/tests/unit/osc/v2/test_validations.py:116 | member_monitor_port_out_of_range, member_set_monitor_port_out_of_range [observed] |
| 14 | A test value is in production code: the project non-uuid skips the lookup | quota show non-uuid calls GET /lbaas/quotas/non-uuid | octaviaclient/osc/v2/utils.py:173-179 | quota_non_uuid_literal [observed] |
| 15 | Path values are not encoded | provider capability list a/b calls /providers/a/b/…; amphora show 'a?b=c' sends ?b=c as a query | octaviaclient/api/v2/octavia.py:885-886, osc-lib:osc_lib/api/api.py:404 | name_with_slash_in_path, name_with_query_in_path [observed] |
| 16 | **GET /lbaas/quotas/defaults is not a route of the service**: Octavia treats defaults as a project ID | works, returning defaults because no such project has a row; policy target is defaults, so a non-admin should get 403 | octaviaclient/api/constants.py:61, octavia:octavia/api/v2/controllers/quotas.py:37-41, octavia:octavia/api/v2/controllers/base.py:176-184 | read only; the 403 is [inferred], and reverse-spec-octavia/01-rest-api_stable-2026.2.md reaches the same conclusion. The stub answers this route |
Lesser oddities [observed]:
--no-tagoncreateis accepted and has no effect.setwith no option sends an emptyPUT, which may move a load balancer toPENDING_UPDATEon a real service [inferred].listener set --hsts-preloadskips the check thatcreateapplies; this is defensible, since the listener may already hold a maximum age.- Quota field spelling differs between
setandunset. - A
nextlink that repeats makes_listloop with no delay: 4,693 requests in 5 s inpagination_never_ends. Only a faulty service would cause it. --os-loadbalancer-api-versionvalidates nothing and is not sent.
Items 1 to 13 pass all unit tests, because commands use a mocked API (octaviaclient/tests/unit/osc/v2/test_load_balancer.py:36) and the API tests use well-formed bodies.
6. Timeouts and retries
- The session is the one built by OSC;
make_clientadds no timeout (octaviaclient/osc/plugin.py:37-41). Unless the cloud setsapi_timeout, a stuck service blocks the command [inferred; default read in python-openstackclient's report]. - keystoneauth's
connect_retriesdefaults to 0 (keystoneauth:keystoneauth1/session.py:408). The plugin sets no retry. - Connection failures carry no
response, so they pass through the decorator unchanged (octaviaclient/api/v2/octavia.py:54-55) [inferred].
Source: reverse-spec-python-octaviaclient/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.