REST API — neutron-fwaas — branch stable/2026.2
Branch
stable/2026.2@5303a4deed1e— repository https://opendev.org/openstack/neutron-fwaas — analysed on 2026-10-28. Other repositories cited:neutron-lib:= neutron-libstable/2026.2@1ee72052251a;neutron:= neutronstable/2026.2@eef1a0f7ee36.
neutron-fwaas serves no HTTP of its own. Its API is the FWaaS v2 extension of neutron-server:
- the resources and their attributes are declared in neutron-lib (
neutron-lib:neutron_lib/api/definitions/firewall_v2.py:51); - the extension descriptor turns them into Routes resources under the prefix
/fwaas(neutron_fwaas/extensions/firewall_v2.py:119, prefixneutron-lib:neutron_lib/api/definitions/firewall_v2.py:49); - Neutron's generic controller handles every request (
neutron:neutron/api/v2/base.py:445for create,neutron:neutron/api/v2/base.py:227for member actions) and calls the service pluginFirewallPluginV2(neutron_fwaas/services/firewall/fwaas_plugin_v2.py:42); - the plugin delegates persistence and back-end programming to the service driver selected by
[service_providers] service_provider = FIREWALL_V2:…:default(neutron_fwaas/services/firewall/fwaas_plugin_v2.py:59). There are two drivers: the OVN driver (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:31, no agent) and the agent driver (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:162, RPC to L3/L2 agent extensions).
The route table below was extracted at run time from the real paste pipeline with an ML2/OVN profile and the OVN driver (_rewrite/routes_stable-2026.2.json, tool _rewrite/tools/fwaas_routes.py). The paths are those of the public endpoint (/v2.0 prefix).
Summary
| Method | Path | Plugin method | Policy (default) | Success | Errors (see 02) |
|---|---|---|---|---|---|
| GET | /v2.0/fwaas/firewall_groups | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:347 get_firewall_groups | get_firewall_group (admin, project reader, or shared) | 200 | 401 |
| GET | /v2.0/fwaas/firewall_groups/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:340 get_firewall_group | get_firewall_group | 200 | 401, 404 |
| POST | /v2.0/fwaas/firewall_groups | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307 create_firewall_group | create_firewall_group (admin or project member); create_firewall_group:shared admin | 201 | 400, 401, 403, 404, 409, 500 |
| PUT | /v2.0/fwaas/firewall_groups/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:358 update_firewall_group | update_firewall_group; :shared admin | 200 | 400, 401, 403, 404, 409, 500 |
| DELETE | /v2.0/fwaas/firewall_groups/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:323 delete_firewall_group | delete_firewall_group; :shared admin | 204 | 401, 403, 404, 409 |
| GET | /v2.0/fwaas/firewall_policies | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:399 get_firewall_policies | get_firewall_policy | 200 | 401 |
| GET | /v2.0/fwaas/firewall_policies/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:392 get_firewall_policy | get_firewall_policy | 200 | 401, 404 |
| POST | /v2.0/fwaas/firewall_policies | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:379 create_firewall_policy | create_firewall_policy; :shared admin | 201 | 400, 401, 403, 404, 409 |
| PUT | /v2.0/fwaas/firewall_policies/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:405 update_firewall_policy | update_firewall_policy; :shared admin | 200 | 400, 401, 403, 404, 409 |
| DELETE | /v2.0/fwaas/firewall_policies/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:385 delete_firewall_policy | delete_firewall_policy | 204 | 401, 404, 409 |
| PUT | /v2.0/fwaas/firewall_policies/{id}/insert_rule | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:448 insert_rule | insert_rule | 200 | 400, 401, 403, 404, 409 |
| PUT | /v2.0/fwaas/firewall_policies/{id}/remove_rule | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:456 remove_rule | remove_rule | 200 | 400, 401, 403, 404, 409 |
| GET | /v2.0/fwaas/firewall_rules | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:434 get_firewall_rules | get_firewall_rule | 200 | 401 |
| GET | /v2.0/fwaas/firewall_rules/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:427 get_firewall_rule | get_firewall_rule | 200 | 401, 404 |
| POST | /v2.0/fwaas/firewall_rules | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:414 create_firewall_rule | create_firewall_rule; :shared admin | 201 | 400, 401, 403, 409 |
| PUT | /v2.0/fwaas/firewall_rules/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:440 update_firewall_rule | update_firewall_rule; :shared admin | 200 | 400, 401, 403, 404, 409, 500 |
| DELETE | /v2.0/fwaas/firewall_rules/{id} | neutron_fwaas/services/firewall/fwaas_plugin_v2.py:420 delete_firewall_rule | delete_firewall_rule; :shared admin | 204 | 401, 404, 409 |
The policies are defined in neutron_fwaas/policies/firewall_group.py:29, neutron_fwaas/policies/firewall_policy.py:30 and neutron_fwaas/policies/firewall_rule.py:30 (full matrix in _rewrite/policies_stable-2026.2.json). Member and reader roles are scoped to the project (scope_types=['project']), and the :shared variants require admin. Read access also matches the shared_firewall_* rules (field:<collection>:shared=True, neutron_fwaas/policies/firewall_group.py:23).
Cross-cutting behaviour (all endpoints)
These come from Neutron, not from neutron-fwaas, but a client sees them on every FWaaS call:
- Authorization hides resources. A GET, PUT, DELETE or member action on a resource the caller may not see returns 404 "The resource could not be found.", never 403 (
neutron:neutron/api/v2/base.py:400,neutron:neutron/api/v2/base.py:598,neutron:neutron/api/v2/base.py:683,neutron:neutron/api/v2/base.py:240). A 403 is only returned when the resource is visible but the action is not allowed (for example settingsharedwithout admin). - No bulk create. The three collections are built without bulk support (
allow_bulk: falsein_rewrite/routes_stable-2026.2.json). A list body returns 400 "Bulk operation not supported" (neutron:neutron/api/v2/base.py:731). - Pagination and sorting are accepted but emulated in Python, not pushed to SQL (
native_pagination: false,native_sorting: falsein the route table). - Unknown attributes in a body return 400 "Unrecognized attribute(s)", and attributes with
allow_post/allow_put: falsereturn 400 (neutron:neutron/api/v2/base.py:712). - Quotas are checked on every create (
register_quota=True,neutron_fwaas/extensions/firewall_v2.py:126; reservation inneutron:neutron/api/v2/base.py:491). Defaults:[QUOTAS] quota_firewall_group = 10,quota_firewall_policy = 10,quota_firewall_rule = 100(neutron_fwaas/extensions/firewall_v2.py:97). Exceeding them returns 409OverQuota. - Notifications: Neutron emits
firewall_group.create.start/.end,.update.start/.endand.delete.start/.end(likewise forfirewall_policyandfirewall_rule) on the notification bus (neutron:neutron/api/v2/base.py:439,neutron:neutron/api/v2/base.py:511,neutron:neutron/api/v2/base.py:628,neutron:neutron/api/v2/base.py:701,neutron:neutron/api/v2/base.py:572,neutron:neutron/api/v2/base.py:605). Member actions (insert_rule,remove_rule) emit none (neutron:neutron/api/v2/base.py:227). - Standard attributes (
created_at,updated_at,revision_number,tags,description) are present on the three resources, because the models carrystandard_attr_idandtag_support = True(neutron_fwaas/db/firewall/v2/models.py:25). However, the extensionstandard-attr-fwaas-v2is not advertised: the moduleneutron_fwaas/extensions/firewall_v2_stdattrs.pydefines the classStandard_attr_fwaas(neutron_fwaas/extensions/firewall_v2_stdattrs.py:19), and Neutron's loader expectsFirewall_v2_stdattrs. It logs "Did not find expected name" (neutron:neutron/api/extensions.py:459) and skips the file (seen at run time, see 07 § 8). - Implicit default firewall group (side effect on reads). Every list call (
GET firewall_groups,GET firewall_policies,GET firewall_rules), and every group creation, first ensures that the caller's project has a firewall group nameddefault, and creates it if needed (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:853,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:566,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:379,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:724). See § "Default firewall group" below.
Resources and attributes
Source of the attribute maps: neutron-lib:neutron_lib/api/definitions/firewall_v2.py:52 (rules), neutron-lib:neutron_lib/api/definitions/firewall_v2.py:116 (groups), neutron-lib:neutron_lib/api/definitions/firewall_v2.py:165 (policies). "POST/PUT" means the attribute is accepted on create/update; every attribute listed is returned.
firewall_rule
| Attribute | POST | PUT | Default | Validation / conversion | Filter |
|---|---|---|---|---|---|
| id | — | — | generated | uuid | yes |
| tenant_id / project_id | yes (admin for another project) | — | caller's project | string ≤ 255 | yes |
| name | yes | yes | "" | name string ≤ 255 | yes |
| description | yes | yes | "" | string ≤ 255 | no |
| firewall_policy_id | — | — | — | read-only; list of the policies that use the rule, or null (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:374, neutron_fwaas/objects/firewall_v2.py:115) | no |
| shared | yes | yes | false | boolean; setting it is governed by create/update_firewall_rule:shared (admin) | yes |
| protocol | yes | yes | null (any) | one of the iptables protocol names (tcp, udp, icmp, ipv6-icmp, sctp, gre, ah, esp, vrrp…) or null (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:77, neutron-lib:neutron_lib/api/definitions/constants.py:29); numbers are refused by the value validator | yes |
| ip_version | yes | yes | 4 | 4 or 6 | yes |
| source_ip_address / destination_ip_address | yes | yes | null | IP or CIDR | yes |
| source_port / destination_port | yes | yes | null | "N" or "min:max", 0–65535, min ≤ max (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:95) | no |
| position | — | — | null | declared but never filled: always null in responses [verified in the exchanges] | no |
| action | yes | yes | deny | allow, deny, reject, case-insensitive (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:107) | yes |
| enabled | yes | yes | true | boolean | yes |
firewall_policy
| Attribute | POST | PUT | Default | Validation | Filter |
|---|---|---|---|---|---|
| id, tenant_id, name, description, shared | as above | ||||
| firewall_rules | yes | yes | [] | ordered list of rule UUIDs; the order is the evaluation order (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:189) | yes |
| audited | yes | yes | false | boolean; **reset to false** by any change of the policy or of one of its rules, unless the PUT sets it explicitly (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:539, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:147, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:311) | yes |
firewall_group
| Attribute | POST | PUT | Default | Validation | Filter |
|---|---|---|---|---|---|
| id, tenant_id, name, description, shared | as above | ||||
| admin_state_up | yes | yes | true | boolean | yes |
| status | — | — | computed | ACTIVE, CREATED, DOWN, ERROR, INACTIVE, PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE (neutron_fwaas/objects/firewall_v2.py:26) | yes |
| ports | yes | yes | [] | list of port UUIDs; on PUT the list replaces the previous one (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:141, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:765) | yes |
| ingress_firewall_policy_id / egress_firewall_policy_id | yes | yes | null | uuid or null | yes |
Direction semantics. "Ingress" means traffic going towards the project network from the router, and "egress" traffic coming from it:
- with iptables, ingress rules match
-o qr-<port>, the router's output to the internal interface (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:50); - with OVN, ingress rules become
from-lportACLs matchinginport == @<port group>on the router-type logical switch port (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:27,neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:127). That port's traffic enters the logical switch from the router, so the meaning is the same. [inferred: the equivalence of the two back ends was deduced from the code, not tested on a live system]
Firewall rules
POST /v2.0/fwaas/firewall_rules
Functional description
- Purpose: define one filtering criterion (protocol, addresses, ports, action) that can then be placed in one or more policies.
- Actor: project member (
create_firewall_rule,neutron_fwaas/policies/firewall_rule.py:30); only an admin can create asharedrule (neutron_fwaas/policies/firewall_rule.py:82). - Preconditions: quota
firewall_rulenot exhausted. - Business rules (
neutron_fwaas/db/firewall/v2/firewall_db_v2.py:248):- ports are allowed only with
tcporudp. Ports with another protocol raiseFirewallRuleInvalidICMPParameter(400,neutron_fwaas/common/utils.py:29); this includessctp, even though OVN could match SCTP ports. Ports without a protocol raiseFirewallRuleWithPortWithoutProtocolInvalid(400,neutron_fwaas/common/utils.py:33); - source and destination addresses must have the rule's
ip_version, otherwiseFirewallIpAddressConflict(400,neutron_fwaas/common/utils.py:48); - a port range must have min ≤ max. The API validator answers first with "First port in a port range must be lower than the second port" (400); the plugin check
FirewallRuleInvalidPortValue(neutron_fwaas/common/utils.py:54) is a second line of defence; - the range is stored as two integers, and a range whose two bounds are equal is shown back as a single port (
neutron_fwaas/common/utils.py:67).
- ports are allowed only with
- Flow:
- (synchronous) API validation, policy, quota (Neutron).
- (synchronous) Business validation and insertion into
firewall_rules_v2(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:257), inside the driver's transaction (neutron_fwaas/services/firewall/service_drivers/driver_api.py:318). - (synchronous) Driver pre/post-commit hooks: neither driver acts on rule creation; a rule only takes effect once it is in a policy that is attached to a group.
- (synchronous) Internal callback
firewall_rule AFTER_CREATE(neutron_fwaas/services/firewall/service_drivers/driver_api.py:331).
- Outcome: the rule exists, with
firewall_policy_id: null. 201. - Limitations: a protocol name that has no OVN mapping (
gre,ah,esp,vrrp…) is accepted, but the OVN driver writes no protocol clause for it, so the ACL matches all IP traffic of that version (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:82; proved by the generated vectors_rewrite/cases-ovn-acl-matrix_stable-2026.2.json).
Technical sheet
- Request:
{"firewall_rule": {"name": "web", "protocol": "tcp", "destination_port": "80:81", "action": "allow", "ip_version": 4, "source_ip_address": "10.0.0.0/24"}}. - Response 201:
{"firewall_rule": {"id": "…", "name": "web", "description": "", "protocol": "tcp", "ip_version": 4, "source_ip_address": "10.0.0.0/24", "destination_ip_address": null, "source_port": null, "destination_port": "80:81", "action": "allow", "enabled": true, "shared": false, "firewall_policy_id": null, "position": null, "project_id": "…", "tenant_id": "…", "created_at": "…", "updated_at": "…", "revision_number": 0, "tags": []}}(shape from_rewrite/response-schemas_stable-2026.2.json). - Errors: 400 (validation above), 403 (
sharedwithout admin), 409OverQuota.
PUT /v2.0/fwaas/firewall_rules/{id}
- Purpose: change a rule; the change is propagated to every firewall group that uses it, through its policies.
- Preconditions: no firewall group that uses the rule (through any policy of the same project as the rule) may be in a
PENDING_*state, otherwiseFirewallGroupInPendingState(409,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:114,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:97). Policies of other projects that use a shared rule are not checked (filter on the rule'sproject_id,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:237). - Business rules: the same validations as on create, applied to the stored rule merged with the change (
neutron_fwaas/db/firewall/v2/firewall_db_v2.py:280). Every policy that contains the rule getsaudited = false(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:308). - Flow:
- (synchronous) DB update.
- (synchronous, OVN driver) If one of
enabled,protocol,ip_version, addresses, ports oractionchanged (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:320):- enabling the rule adds its ACL to every port group concerned;
- disabling it deletes the ACL;
- otherwise the ACL
matchandactioncolumns are rewritten in place. The priority is unchanged.
- (asynchronous, agent driver) For each policy that contains the rule, each group with ports goes to
PENDING_UPDATE, and a fanout castupdate_firewall_groupcarries the full rule set to the agents (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:350,neutron_fwaas/services/firewall/service_drivers/agents/agents.py:207). The agents report the status back (05 § "update_firewall_group").
- Outcome: 200 with the rule. With OVN the ACLs are already changed when the response is sent. With agents the groups are
PENDING_UPDATEuntil the agents answer. - Limitations (OVN): the OVN update looks for the ACLs of the policies listed in the old rule's
firewall_policy_id(neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:331). If the policy list is empty, nothing happens.
DELETE /v2.0/fwaas/firewall_rules/{id}
- Purpose: delete a rule that no policy uses.
- Business rules: a rule still associated with a policy raises
FirewallRuleInUse(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:320). Unknown id:FirewallRuleNotFound(404,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:74). - Flow: DB delete (
neutron_fwaas/db/firewall/v2/firewall_db_v2.py:315); callbackfirewall_rule AFTER_DELETE(neutron_fwaas/services/firewall/service_drivers/driver_api.py:353). No back-end action. - Outcome: 204.
GET /v2.0/fwaas/firewall_rules[/{id}]
- Purpose: read the rules the caller may see (own project, plus shared rules).
- Side effect: the list call creates the caller's default firewall group if missing (§ "Default firewall group"). In a project without one, the first list therefore returns the four rules
default ingress ipv4/ipv6anddefault egress ipv4/ipv6. - Filters: every attribute marked "Filter" above.
firewall_policy_idcannot be filtered.
Firewall policies
POST /v2.0/fwaas/firewall_policies
- Purpose: group an ordered list of rules into a policy that can be attached to firewall groups as their ingress and/or egress policy.
- Actor: project member;
sharedrequires admin (neutron_fwaas/policies/firewall_policy.py:82). - Business rules:
- the names
default ingressanddefault egressare reserved:FirewallDefaultParameterExists(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:519,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:841); - every rule must exist:
FirewallRuleNotFound(404,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:417); - a shared policy may only contain shared rules:
FirewallRuleSharingConflict. It is returned with 404, because neutron-lib derives it fromNotFound(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:421,neutron-lib:neutron_lib/exceptions/firewall_v2.py:64); - an unshared rule must belong to the policy's project:
FirewallRuleConflict(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:432); - the position of each rule is its index in
firewall_rules, starting at 1 (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:402).
- the names
- Flow: insertion into
firewall_policies_v2andfirewall_policy_rule_associations_v2(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:502); no back-end action (a new policy is attached to no group). - Outcome: 201.
PUT /v2.0/fwaas/firewall_policies/{id}
- Purpose: rename, (un)share, mark audited, or replace the rule list of a policy; the result is propagated to the groups that use it.
- Preconditions: no group of the policy's project that uses it is
PENDING_*(409FirewallGroupInPendingState,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:101). - Business rules (
neutron_fwaas/db/firewall/v2/firewall_db_v2.py:522):- the default policies cannot be updated at all:
FirewallDefaultObjectUpdateRestricted(409; its message says "of type Firewall Group" even for a policy,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:839); - un-sharing (
shared: false) is refused when a group of another project uses the policy:FirewallPolicyInUse(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:467); - sharing without changing the rules requires all current rules to be shared:
FirewallPolicySharingConflict(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:443); - a new
firewall_ruleslist is validated as on create, then replaces the associations (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:474). An empty list removes all rules; auditedbecomesfalseunless the body sets it (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:539).
- the default policies cannot be updated at all:
- Flow (OVN): when the rule list changed, every port group of a group that uses the policy is rebuilt: its ACLs are reset to the four default drops, then the ACLs of both policies of the group are re-added (
neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:311,neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:157). If the port group is missing, it is created. - Flow (agents): a
PENDING_UPDATEand fanout cast for each group with ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:346). - Outcome: 200 with the policy.
PUT /v2.0/fwaas/firewall_policies/{id}/insert_rule
- Purpose: insert one rule at a precise place in the policy, without resending the whole list.
- Request:
{"firewall_rule_id": "<uuid>", "insert_before": "<uuid>"}or{"firewall_rule_id": "<uuid>", "insert_after": "<uuid>"}. Both are optional. When both are given,insert_beforewins. When neither is given, the rule goes first (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:323). - Business rules:
firewall_rule_idmissing:FirewallRuleInfoMissing(400). Not a UUID:FirewallRuleNotFound(404) (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:243);- the rule is already in the policy:
FirewallRuleAlreadyAssociated(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:161); - the rule is unshared and belongs to another project:
FirewallRuleConflict(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:119); - the reference rule is not in the policy:
FirewallRuleNotAssociatedWithPolicy(400,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:175); - the policy's groups must not be
PENDING_*(409); - a shared policy accepts an unshared rule here (no sharing check, unlike PUT) [verified:
insert_ruleonly calls_check_firewall_rule_conflict,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:338].
- Flow: the associations are deleted and rewritten with positions 1..n, and
auditedbecomesfalse(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:123). OVN: the port groups of the policy's groups are rebuilt (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:356). Agents:PENDING_UPDATEand a cast (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:357). - Response 200: the policy without the
firewall_policywrapper ({"id": …, "firewall_rules": [...], …}): the member action returns the plugin result as is (neutron:neutron/api/v2/base.py:262) [verified in the exchanges].
PUT /v2.0/fwaas/firewall_policies/{id}/remove_rule
- Request:
{"firewall_rule_id": "<uuid>"}. - Business rules: as for
insert_rule. The rule must be associated with the policy, otherwise 400FirewallRuleNotAssociatedWithPolicy(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:361). - Flow: the positions are renumbered and
auditedbecomesfalse. OVN: the rule's ACL is deleted from each port group (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:360). Agents: a cast. - Response 200: the unwrapped policy.
DELETE /v2.0/fwaas/firewall_policies/{id}
- Business rules: refused with
FirewallPolicyInUse(409) while any group, of any project, uses the policy as ingress or egress policy (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:548). The rule associations are deleted with it (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:554). - Note: the internal callback published after a policy deletion is
AFTER_UPDATE, notAFTER_DELETE(neutron_fwaas/services/firewall/service_drivers/driver_api.py:272). - Outcome: 204.
Firewall groups
POST /v2.0/fwaas/firewall_groups
Functional description
- Purpose: apply an ingress and/or egress policy to a set of ports: router interfaces (L3) or VM ports (L2, agent driver with OVS only).
- Actor: project member;
sharedrequires admin (neutron_fwaas/policies/firewall_group.py:81). - Preconditions:
- each policy exists and is either shared or of the group's project:
FirewallPolicyNotFound(404) orFirewallPolicyConflict(404,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:141,neutron-lib:neutron_lib/exceptions/firewall_v2.py:54); - each port exists, is a router interface port (
network:router_interface,network:router_interface_distributed,network:ha_router_replicated_interface) or a compute port (compute:*), and is supported by the driver (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:144):- another
device_ownerraisesFirewallGroupPortInvalid(409); - a port the driver does not support raises
FirewallGroupPortNotSupported(409,neutron_fwaas/common/exceptions.py:22). The OVN driver supports router ports only (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:42). The agent driver supports compute ports bound withvif_type=ovsand no hybrid plug (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:182); an unbound or failed-binding port is refused (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:178);
- another
- no port may already belong to another group of the project:
FirewallGroupPortInUse(409,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:207). The database enforces it globally with a unique key onport_id(neutron_fwaas/db/firewall/v2/models.py:97), which raises the same error (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:596); - the port ownership is not checked against the group's project. The plugin reads the port with the caller's context (
neutron_fwaas/services/firewall/fwaas_plugin_v2.py:157), so a member can only name ports they can see, but an admin can attach any port [inferred].FirewallGroupPortInvalidProjectexists in neutron-lib but is never raised.
- each policy exists and is either shared or of the group's project:
- Business rules:
- the name
defaultis reserved:FirewallDefaultParameterExists(409,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:832); - creating any group first creates the project's default group if missing (
neutron_fwaas/db/firewall/v2/firewall_db_v2.py:724).
- the name
- Flow (OVN driver):
- (synchronous, inside the DB transaction) Insertion of the group and port associations with status
CREATED(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:713). - (synchronous, pre-commit) The status becomes
INACTIVEwithout ports,PENDING_CREATEwith ports. The OVN Port_Grouppg_<uuid with _>is created withexternal_ids:neutron:firewall_group_id, and the four default drop ACLs are added in separate OVSDB transactions (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:199,neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:219). - (synchronous, "post-commit") One OVSDB transaction adds the ACLs of the policies' rules and the ports to the Port_Group. On success the status becomes
ACTIVEif there are ports (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:210). On failure the Port_Group is deleted and the error propagates (500). Despite its name, this hook still runs inside the plugin method's@db_api.CONTEXT_WRITERtransaction (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:307), which the driver's innerCONTEXT_WRITER.using(context)joins (neutron_fwaas/services/firewall/service_drivers/driver_api.py:143). So on failure the group row is rolled back too. Only the project's default group, created in its own admin transaction, stays [inferred from the decorator nesting].
- (synchronous, inside the DB transaction) Insertion of the group and port associations with status
- Flow (agent driver):
- (synchronous) The status becomes
INACTIVEwithout ports or without any policy, otherwisePENDING_CREATE. It becomesCREATEDwhenrouter_distributed = True(neutron_fwaas/services/firewall/service_drivers/agents/agents.py:254). - (asynchronous) If not
INACTIVE, a fanout castcreate_firewall_groupwith the group, its ordered rules and the details of its ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:268). - (asynchronous) Each L3 agent hosting one of the router ports programs iptables and answers
set_firewall_group_status(ACTIVE|DOWN|ERROR)(05 § "create_firewall_group (L3 agent)").
- (synchronous) The status becomes
- Outcome: OVN:
ACTIVEorINACTIVEin the response, and the ACLs are in the OVN Northbound DB. Agents:PENDING_CREATE,INACTIVEorCREATED; the finalACTIVE/DOWN/ERRORis observed with GET. - Effects visible elsewhere: the internal callback
firewall_group AFTER_CREATE(neutron_fwaas/services/firewall/service_drivers/driver_api.py:158) drives the FWaaS logging extension when the log plugin is loaded (neutron_fwaas/services/logapi/common/fwg_callback.py:27).
Technical sheet
- Request:
{"firewall_group": {"name": "fwg1", "ingress_firewall_policy_id": "<uuid>", "egress_firewall_policy_id": "<uuid>", "ports": ["<router interface port uuid>"], "admin_state_up": true}}. - Response 201:
{"firewall_group": {"id": …, "name": …, "description": "", "status": "ACTIVE", "admin_state_up": true, "shared": false, "ports": [...], "ingress_firewall_policy_id": …, "egress_firewall_policy_id": …, "project_id": …, "tenant_id": …, "created_at": …, "updated_at": …, "revision_number": …, "tags": []}}. - Limitations:
admin_state_up: falseis stored but ignored by the OVN driver: the ACLs are applied anyway (no reference toadmin_state_upinneutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py);- with the agent driver,
admin_state_up: falseinstalls the default drop-all policy (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:158).
PUT /v2.0/fwaas/firewall_groups/{id}
- Purpose: change the ports or policies of a group, or its name, description,
admin_state_upandshared. - Preconditions: the group is not
PENDING_*(409FirewallGroupInPendingState,neutron_fwaas/services/firewall/fwaas_plugin_v2.py:362). - Business rules:
- the same policy and port checks as on create, ignoring the group itself for the "port in use" check (
neutron_fwaas/services/firewall/fwaas_plugin_v2.py:370); - the name cannot become
default(409FirewallDefaultParameterExists,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:750); - on the default group, a member may only change
portsandshared(subject to policy), and an admin may change everything exceptname. Otherwise 409FirewallDefaultObjectUpdateRestricted(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:753); portsreplaces the whole list (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:765).
- the same policy and port checks as on create, ignoring the group itself for the "port in use" check (
- Flow (OVN): if the ports or a policy changed, the status goes to
PENDING_UPDATE, then synchronously toACTIVEorINACTIVE(no ports) once the OVSDB transaction succeeds (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:238,neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:251):- ports added and removed are applied to the Port_Group;
- a policy change resets the ACLs to the four defaults and re-adds the rules;
- a missing Port_Group is recreated.
- Flow (agents):
PENDING_UPDATEwhen the ports changed and the group has a policy, or when a policy changed and the group has ports (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:278). Then a fanout castupdate_firewall_groupwithadd-port-ids,del-port-idsandlast-port(neutron_fwaas/services/firewall/service_drivers/agents/agents.py:300). Special case: adding one non-ACTIVEport to the default group sends no cast; the L2 agent applies it when the port comes up (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:327). - Outcome: 200. OVN: final status in the response. Agents:
PENDING_UPDATEuntil the agents report.
DELETE /v2.0/fwaas/firewall_groups/{id}
- Business rules:
- a group that still has ports cannot be deleted:
FirewallGroupInUse(409, message "is still active",neutron_fwaas/services/firewall/fwaas_plugin_v2.py:332); - an unknown id returns 204, not 404 (
neutron_fwaas/services/firewall/fwaas_plugin_v2.py:326). In practice Neutron's controller fetches the resource first for the policy check, and returns 404 for an id that does not exist (neutron:neutron/api/v2/base.py:582) [inferred: the plugin branch only matters for a concurrent delete]; - the default group can only be deleted by an admin, otherwise 409
FirewallGroupCannotRemoveDefault(neutron_fwaas/db/firewall/v2/firewall_db_v2.py:813). When an admin deletes it, its two default policies are deleted too, but their four rules are left behind [verified:neutron_fwaas/db/firewall/v2/firewall_db_v2.py:806deletes only the policies].
- a group that still has ports cannot be deleted:
- Flow: OVN: the Port_Group is deleted before the DB row (
neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:305). Agents: the row is deleted; no cast is sent, since a deletable group has no ports (neutron_fwaas/services/firewall/service_drivers/driver_api.py:170). Then the callbackfirewall_group AFTER_DELETE. - Outcome: 204.
GET /v2.0/fwaas/firewall_groups[/{id}]
- **Filter on
ports**:?ports=<port uuid>returns the group attached to the port, through the association table (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:855). - Side effect: creates the default group of the caller's project if missing.
Default firewall group
For each project, neutron-fwaas lazily creates (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:643):
- four rules,
default ingress ipv4,default ingress ipv6,default egress ipv4,default egress ipv6, built from the[default_fwg_rules]options. By default, ingress isdenyand egress isallow, any protocol and address (neutron_fwaas/extensions/firewall_v2.py:28,neutron_fwaas/db/firewall/v2/firewall_db_v2.py:180); - the policies
default ingressanddefault egress; - the group
default(statusINACTIVE, no ports), recorded indefault_firewall_groups(primary keyproject_id,neutron_fwaas/db/firewall/v2/models.py:76).
It is created with an admin context, in its own transaction. A concurrent creation is detected by the unique key and ignored (neutron_fwaas/db/firewall/v2/firewall_db_v2.py:698). It is triggered by the list calls and by the creation of any group. The default group is created without driver calls (_create_firewall_group is called directly, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:688):
- with OVN, it gets a Port_Group only once it is updated with ports;
- the OVN DB sync skips it (
neutron_fwaas/services/firewall/service_drivers/ovn/ovn_db_sync.py:89).
Automatic attachment of new VM ports: when a compute port is bound for the first time (its old binding:vif_type was unbound) and the driver supports it as an L2 port, the plugin adds it to the project's default group (neutron_fwaas/services/firewall/fwaas_plugin_v2.py:257). With the OVN driver is_supported_l2_port is always false, so this never happens on OVN.
Lifecycle: firewall_group (OVN driver)
stateDiagram-v2 [*] --> CREATED: POST (DB insert) CREATED --> INACTIVE: pre-commit, no ports CREATED --> PENDING_CREATE: pre-commit, ports given PENDING_CREATE --> ACTIVE: post-commit OVSDB txn OK (same request) PENDING_CREATE --> [*]: OVSDB txn fails (500, Port_Group and row rolled back) ACTIVE --> PENDING_UPDATE: PUT ports or policies INACTIVE --> PENDING_UPDATE: PUT ports or policies PENDING_UPDATE --> ACTIVE: OVSDB txn OK, ports PENDING_UPDATE --> INACTIVE: OVSDB txn OK, no ports ACTIVE --> [*]: DELETE (only without ports) INACTIVE --> [*]: DELETE
Lifecycle: firewall_group (agent driver)
stateDiagram-v2 [*] --> INACTIVE: POST without ports or without policy [*] --> PENDING_CREATE: POST with ports and policy (legacy routers) [*] --> CREATED: POST with ports and policy, router_distributed=True PENDING_CREATE --> ACTIVE: set_firewall_group_status (L3/L2 agent, admin_state_up) PENDING_CREATE --> DOWN: set_firewall_group_status (admin_state_up=false) PENDING_CREATE --> ERROR: driver error on agent (FirewallInternalDriverError) ACTIVE --> PENDING_UPDATE: PUT ports/policies, rule or policy change PENDING_UPDATE --> ACTIVE: set_firewall_group_status PENDING_UPDATE --> INACTIVE: last port removed (last-port) PENDING_UPDATE --> ERROR: driver error on agent ERROR --> [*]: firewall_group_deleted (agent) ACTIVE --> [*]: DELETE (only without ports) INACTIVE --> [*]: DELETE
A status sent by an agent that is not ACTIVE, DOWN or INACTIVE is stored as ERROR, and a group in PENDING_DELETE keeps that status (neutron_fwaas/services/firewall/service_drivers/agents/agents.py:51). Nothing in the server sets PENDING_DELETE any more, so that branch is dead [verified: no assignment of PENDING_DELETE outside the agent-side sync code].
Spec / code gaps
| Gap | Declared | Code |
|---|---|---|
Error codes of POST /fwaas/firewall_groups | api-ref: 400, 401 (neutron-lib:api-ref/source/v2/fwaas-v2.inc:138) | also 403, 404 (FirewallPolicyNotFound, FirewallPolicyConflict), 409 (port checks, reserved name, quota) |
DELETE of an unknown group | api-ref: 404 (neutron-lib:api-ref/source/v2/fwaas-v2.inc:259) | plugin returns silently; Neutron's pre-fetch gives 404 |
insert_rule/remove_rule response | api-ref documents a policy body | body is not wrapped in firewall_policy |
position of a rule | listed as an attribute | always null |
standard-attr-fwaas-v2 extension | defined in neutron-lib and in neutron_fwaas/extensions/firewall_v2_stdattrs.py:19 | never loaded (class name), not listed in /v2.0/extensions |
Routes generated by mapper.resource | — | GET …/new and GET …/{id}/edit exist for the three collections and have no controller method (routes_artifact in the route table) |
Source: reverse-spec-neutron-fwaas/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.