OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

neutron-fwaas — Rewrite pack

Rewrite pack — 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: neutron-lib: @ 1ee72052251a, neutron: @ eef1a0f7ee36, openstacksdk: @ fbdc403819e0, python-openstackclient: @ 8bb083b83829, skyline-console: @ 806d200392ee, heat: @ 5872396e548c (all stable/2026.2 clones in the OpenKeel workspace).

For OpenKeel, the target is an FWaaS v2 API served by the Go Neutron replacement, programming OVN Northbound ACLs (Kube-OVN or OVN). The agent path (iptables and OVS flows) is documented in files 01–05 for completeness. A Kubernetes-native rewrite does not need it.

1. Contents of _rewrite/

FileUse in a rewrite
[routes_stable-2026.2.json](_rewrite/routes_stable-2026.2.json)route table extracted from the running Neutron pipeline (ML2/OVN + firewall_v2 + OVN driver): 17 real operations plus 6 Routes artifacts (/new, /{id}/edit), plugin method and file:line, policy, status, and the run-time attribute map of the three collections
[openapi_stable-2026.2.json](_rewrite/openapi_stable-2026.2.json)OpenAPI 3.0.3 built from the route table and the observed response schemas. x-neutron-validate and x-neutron-convert-to carry Neutron's validators; x-response-unwrapped marks insert_rule/remove_rule. Types and stubs only, not the authority on behaviour
[api-ref_stable-2026.2.json](_rewrite/api-ref_stable-2026.2.json)the 17 operations documented in neutron-lib:api-ref/source/v2/fwaas-v2.inc, with parameters and samples (paths prefixed neutron-lib:)
[policies_stable-2026.2.json](_rewrite/policies_stable-2026.2.json), [policies_stable-2026.2.yaml](_rewrite/policies_stable-2026.2.yaml)the 26 default oslo.policy rules with their deprecated forms
[config_stable-2026.2.conf](_rewrite/config_stable-2026.2.conf)server options of neutron-fwaas: [default_fwg_rules], [quotas], [service_providers]
[config-agent_stable-2026.2.conf](_rewrite/config-agent_stable-2026.2.conf)agent options [fwaas] (driver, enabled, conntrack_driver, firewall_l2_driver); not needed on OVN
[schema-mysql-migrated_stable-2026.2.sql](_rewrite/schema-mysql-migrated_stable-2026.2.sql)authoritative schema: the six FWaaS tables as created by neutron-db-manage upgrade heads on MariaDB 11.4
[schema-mysql_stable-2026.2.sql](_rewrite/schema-mysql_stable-2026.2.sql), [schema-postgresql_stable-2026.2.sql](_rewrite/schema-postgresql_stable-2026.2.sql)DDL generated from the ORM models (tool _rewrite/tools/fwaas_model_ddl.py)
[cases-ovn-acl_stable-2026.2.json](_rewrite/cases-ovn-acl_stable-2026.2.json)67 vectors of the OVN ACL translation recorded from the OVN driver unit tests (rule dict + direction → ACL row); replay 67/67
[cases-ovn-acl-matrix_stable-2026.2.json](_rewrite/cases-ovn-acl-matrix_stable-2026.2.json)868 generated vectors (every action × tcp/udp/sctp/icmp/ipv6-icmp/gre/any × IPv4/IPv6 × port forms × addresses × directions × positions 0 and 3, plus the 4 default ACLs), computed by the original function; replay 868/868
[cases-iptables-rule_stable-2026.2.json](_rewrite/cases-iptables-rule_stable-2026.2.json)8 vectors of the iptables rule conversion (agent path); replay 8/8
[replay-report_stable-2026.2.json](_rewrite/replay-report_stable-2026.2.json)replay results of the three case sets
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json)691 HTTP exchanges of the API unit tests (135 tests, 0 failures), paths normalised to /v2.0/fwaas/…. Evidence and examples for differential testing, not replayable: no seed
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json), [.md](_rewrite/response-schemas_stable-2026.2.md)response JSON schemas inferred per operation and status
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md)43 (operation, status, type, message) combinations seen in the test run
tools/fwaas_routes.py + neutron-fwaas-ovn.conf (route table), fwaas_model_ddl.py, fwaas_openapi.py, fwaas_http_infer.py, fwaas_vectors.py, and adapters/neutron_fwaas.py (conformance_capture adapter)

Library versions used for the extraction: neutron 29.0.1.dev29 and neutron-lib 5.0.2.dev16 (local clones above), ovsdbapp 2.19.0, oslo.db 18.1.1, oslo.messaging 18.3.1, oslo.policy 6.0.1, SQLAlchemy 2.0.52, alembic 1.19.1, netaddr 1.3.0, Python 3.12.7.

2. HTTP behaviour that the contracts do not show

2.1 Pipeline

FWaaS requests go through Neutron's paste pipeline (neutron:etc/neutron/api-paste.ini:10), outermost first: cors, http_proxy_to_wsgi, request_id, catch_errors, osprofiler, authtoken (keystonemiddleware), keystonecontext, then extensions. That last one is Neutron's Routes ExtensionMiddleware, which matches /fwaas/... and hands the request to Controller (neutron:neutron/api/v2/base.py:445). So:

  • 401 (token) wins over everything;
  • then routing: an unknown path returns 404;
  • then body preparation (400), policy (403/404), quota (409), and finally the plugin.

2.2 Error body

Always {"NeutronError": {"type": "<ExceptionClass>", "message": "<text>", "detail": ""}} (neutron:neutron/api/api_common.py:520). The type values to reproduce exactly are listed in 02 and _rewrite/observed-errors_stable-2026.2.md. Clients display message (openstacksdk openstacksdk:openstack/network/v2/firewall_policy.py:104).

2.3 Other cross-cutting rules

  • Resource wrapper: {"firewall_group": {...}} for create, update and show; {"firewall_groups": [...]} for list. **Exception: insert_rule and remove_rule return the bare policy object** (neutron:neutron/api/v2/base.py:262).
  • **fields= projection**, limit/marker/page_reverse pagination with firewall_groups_links, sort_key/sort_dir (emulated), and filters on is_filter attributes. These are Neutron generic behaviours, to take from the Neutron rewrite.
  • Hidden resources return 404, not 403 (01 § Cross-cutting).
  • tenant_id and project_id are both returned. Standard attributes (created_at, updated_at, revision_number, tags) are returned although the standard-attr-fwaas-v2 extension is not advertised (§ 8).
  • GET /v2.0/extensions lists fwaas_v2 (alias), "FWaaS v2", updated "2016-10-06T10:00:00-00:00" (neutron-lib:neutron_lib/api/definitions/firewall_v2.py:25).

3. Core algorithm: firewall group → OVN Port_Group and ACLs

The behaviour a rewrite must reproduce bit for bit, so that an existing OVN NB DB stays consistent after switching implementations.

3.1 Input model and validation

  • Rule (DB row → dict): id, action ∈ {allow, deny, reject}, protocol (iptables protocol name or null), ip_version ∈ {4, 6}, source_ip_address/destination_ip_address (string as entered: 1.1.1.1 stays 1.1.1.1, not /32, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:37), source_port/destination_port ("N" or "a:b", rebuilt from the min/max columns, neutron_fwaas/objects/firewall_v2.py:105), enabled.
  • Validation (400 cases, all before any OVN write):
    • ports only with tcp/udp (neutron_fwaas/common/utils.py:21);
    • addresses of the rule's IP version (neutron_fwaas/common/utils.py:36);
    • min ≤ max (neutron_fwaas/common/utils.py:51);
    • protocol in the iptables protocol name list (neutron-lib:neutron_lib/api/definitions/constants.py:29).
  • Group: id, ports (router interface port ids), ingress_firewall_policy_id, egress_firewall_policy_id. Each policy gives an ordered rule list (firewall_policy_rule_associations_v2.position ascending, neutron_fwaas/db/firewall/v2/firewall_db_v2.py:77).

3.2 Names and constants

  • pg_name = "pg_" + group_id.replace("-", "_") (neutron:neutron/common/ovn/utils.py:367).
  • Port_Group.external_ids = {"neutron:firewall_group_id": group_id} (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:223).
  • ACL.external_ids = {"neutron:firewall_rule_id": rule_id}, or "default_rule" for the four default drops (neutron_fwaas/services/firewall/service_drivers/ovn/constants.py:19, neutron_fwaas/services/firewall/service_drivers/ovn/constants.py:31).
  • Priorities: user rules 2000 - position, default drops 1001 (neutron_fwaas/services/firewall/service_drivers/ovn/constants.py:24).
  • Actions: allow → allow-stateless, deny → drop, reject → reject (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:99).
  • Direction: ingress → from-lport + inport == @pg; egress → to-lport + outport == @pg (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:27, neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:127).
  • log = false, name = [], severity = [] (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:131).

3.3 ACL of one rule (pure function)

acl(pg, rule, dir):                                    # neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:143
  match = (dir == ingress ? "inport" : "outport") + " == @" + pg
  if rule.ip_version == 4: v="ip4"; icmp="icmp4"; match += " && ip4"
  elif == 6:               v="ip6"; icmp="icmp6"; match += " && ip6"
  if rule.source_ip_address:      match += " && " + v + ".src == " + rule.source_ip_address
  if rule.destination_ip_address: match += " && " + v + ".dst == " + rule.destination_ip_address
  p = rule.protocol
  if p in (tcp, udp, sctp):                            # neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:89
     match += " && " + p
     for (port, side) in ((source_port, "src"), (destination_port, "dst")):
        if port: (a, b) = split(port, ":") (b = a if absent)
           if a == b: match += " && p.side == a"
           else:      match += " && p.side >= a && p.side <= b"
  elif p in (icmp, ipv6-icmp): match += " && " + icmp  # chosen by ip_version, not by p
  # any other p (gre, ah, esp, vrrp, numbers…) adds NOTHING: the ACL matches all traffic of ip_version
  if rule.action == deny and rule.is_default: (action, prio) = (drop, 1001)
  else: prio = 2000 - rule.position; action = {allow: allow-stateless, deny: drop, reject: reject}
  return {port_group: pg, priority: prio, action, log: false, name: [], severity: [],
          direction: (ingress ? from-lport : to-lport), match, "neutron:firewall_rule_id": rule.id}

The 935 vectors of § 1 fix every branch, including ipv6-icmp with ip_version 4 (gives icmp4) and icmp with ip_version 6 (gives icmp6).

3.4 Group programming

  • Position numbering: per direction, the rules of the policy in order get positions 0, 1, 2…; disabled rules consume a position but produce no ACL (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:102).
  • Create (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:199):
    1. pg_add(name, acls=[], external_ids, may_exist) in its own transaction;
    2. the 4 default ACLs (egress then ingress, v4 then v6, neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:228) with pg_acl_add(may_exist=True);
    3. in one transaction, the rule ACLs of both policies, then pg_add_ports for each port;
    4. status ACTIVE with ports, INACTIVE without.
  • Update: if the Port_Group is missing, recreate it as in create. Otherwise apply the port delta (pg_add_ports/pg_del_ports if_exists). On any policy change, reset Port_Group.acls to the 4 default ACL uuids (or re-add them if they are not exactly 4), then re-add all rule ACLs (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:126).
  • Policy rule list changed / insert_rule: same reset and re-add for every group that uses the policy (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:157).
  • remove_rule / rule disabled: find, in pg_acl_list(pg), the ACL whose external_ids rule id and direction match, and pg_acl_del(pg, direction, its priority, recomputed match) (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:198).
  • Rule enabled: pg_acl_add of that rule only, at its current position (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:108).
  • Rule changed: db_set ACL match, action on the matching ACL; the priority is unchanged (neutron_fwaas/services/firewall/service_drivers/ovn/acl.py:206).
  • Delete group: pg_del(if_exists) (neutron_fwaas/services/firewall/service_drivers/ovn/firewall_l3_driver.py:305).
  • **admin_state_up is ignored.** Ports of any kind other than router interfaces are refused upstream.

3.5 Ordering and determinism

  • The ACL set of a Port_Group is fully determined by the policies, their rule order and enabled. The order of ACL creation inside a transaction does not matter (OVN evaluates by priority).
  • Priority collisions: ingress and egress rules both start at 2000, but different directions never collide. A policy with more than 999 rules reaches the 1001 of the default drops, and positions ≥ 2000 give invalid priorities. The default quota of 100 rules per project prevents this in practice [inferred]. A rewrite should reproduce the formula, or refuse more than 999 rules per policy explicitly.
  • Stateless allow: replies are not implicitly allowed. A rewrite that switched to allow-related would change the security semantics of existing deployments.

3.6 Test cases of the algorithm

  • Format: conformance_capture calls cases, {"kind": "ovn-acl", "input": {"port_group", "rule", "direction"}, "expected": <ACL dict>}. The iptables-rule kind has {"input": {"rule"}, "expected": "<iptables args>"}.
  • Coverage:
    • recorded vectors (67): tcp/icmp IPv4, the three actions, both directions, and the 40 default-rule calls;
    • generated vectors (868): udp, sctp, ipv6-icmp, protocols without mapping, IPv6, single ports and ranges, addresses on either side, positions 0 and 3.
  • Replay (adapter _rewrite/tools/adapters/neutron_fwaas.py, replay_case calls the original function): 67/67, 868/868, 8/8 pass (_rewrite/replay-report_stable-2026.2.json).
  • Not encoded: the OVSDB transaction sequence (number and grouping of transactions), the pg_acl_list-based lookups for delete and modify, and the Port_Group port changes. Test those against a real NB DB (§ 7).
  • The iptables vectors embed the action chain name conformance_capt-accepted: neutron-l3-agent derives the prefix from the process name (neutron-l3-agent-accepted in production) (neutron_fwaas/services/firewall/service_drivers/agents/drivers/linux/iptables_fwaas_v2.py:420).

3.7 Performance

Each API write costs at most a few OVSDB transactions per affected group (03 § "Operations per API call"). A policy change rewrites every ACL of every group using it: O(groups × rules) ACL rows per request.

4. What the clients expect

ExpectationWhere the client relies on it
Paths /fwaas/firewall_groups, /fwaas/firewall_policies, /fwaas/firewall_rules under the network endpoint, keys firewall_group(s) etc.openstacksdk:openstack/network/v2/firewall_group.py:20, skyline-console:src/client/neutron/index.js:118
insert_rule/remove_rule return the unwrapped policy: the SDK merges the JSON straight into the resourceopenstacksdk:openstack/network/v2/firewall_policy.py:97
on error, NeutronError.message is the displayed textopenstacksdk:openstack/network/v2/firewall_policy.py:104
insert_rule accepts firewall_rule_id, insert_before, insert_afteropenstacksdk:openstack/network/v2/_proxy.py:6240, skyline-console:src/client/neutron/index.js:128
PUT ports replaces the list; OSC firewall group set --port / unset --port read the current ports and send the full new listpython-openstackclient:openstackclient/network/v2/fwaas/group.py:171, python-openstackclient:openstackclient/network/v2/fwaas/group.py:479
PUT firewall_rules replaces the ordered listpython-openstackclient:openstackclient/network/v2/fwaas/policy.py:61
list filters sent by the SDK include source_port, destination_port and firewall_policy_id, which the server does not declare as filters (is_filter false). The FWaaS plugin does not declare filter-validation support, so Neutron does not reject them (neutron:neutron/api/v2/base.py:156). What the DB layer then does with them is not verified [inferred, not tested]openstacksdk:openstack/network/v2/firewall_rule.py:33
Heat's OS::Neutron::Firewall* resources require the extension alias fwaas (FWaaS v1) and call the v1 API, so they are unavailable against v2: no expectation to keepheat:heat/engine/resources/openstack/neutron/firewall.py:31

5. Database strategy

  • Reuse the migrated schema (_rewrite/schema-mysql-migrated_stable-2026.2.sql, alembic branch neutron-fwaas, heads 1007f519ea46 + 2a0d33e9ef63). It matches the models for columns, keys and foreign keys. Differences:
    • the migration names its unique keys (uniq_firewall_rules_v20standard_attr_id, uniq_firewallgroupportassociation0port_id…) where the model DDL leaves them unnamed;
    • MariaDB adds implicit indexes on the FK columns (ingress_firewall_policy_id, egress_firewall_policy_id, firewall_rule_id, firewall_group_id);
    • column order differs.
  • Shared Neutron tables: each rule, policy and group owns a standardattributes row (resource_type = firewall_rules_v2, firewall_policies_v2 or firewall_groups_v2 [inferred from the Neutron standard-attribute convention], revision_number, timestamps, description). Tags live in Neutron's tags table. firewall_group_port_associations_v2.port_id references Neutron's ports with ON DELETE CASCADE, so deleting a port silently removes it from its group without touching OVN. The router LSP disappears too, so OVN stays consistent.
  • Integrity kept by code:
    • "one group per port" is also a DB unique key;
    • policy in use (FKs without cascade on firewall_groups_v2 would raise an IntegrityError if the code check were skipped);
    • rule in use (cascade FK; the code refuses first);
    • default resources (by name).
  • No optimistic locking: no revision check on PUT, and the read-modify-write of the policy rule list and of the default group's ports has no lock (03 § Notable transactions).
  • Dead column: firewall_policies_v2.rule_count (never written).
  • Startup data: none. The default group is created lazily per project (01 § Default firewall group).

6. Authentication, policies, configuration and CLI

  • Auth: keystonemiddleware in Neutron's pipeline; project-scoped tokens.
  • Policies: the 26 rules of _rewrite/policies_stable-2026.2.json, with oslo.policy semantics:
    • role:member and project_id:%(project_id)s, role:reader…, rule:admin_only;
    • field:firewall_groups:shared=True for read access to shared objects;
    • :shared sub-rules checked by Neutron when the body sets shared (attribute enforcement, enforce_policy: True in the attribute map). The deprecated rules (rule:admin_or_owner, rule:regular_user) are still loaded while enforce_new_defaults is false.
  • Configuration a drop-in must accept:
    • [service_providers] service_provider = FIREWALL_V2:<name>:<driver class>:default;
    • [default_fwg_rules] * (15 options: action, addresses, ports, protocol, shared, enabled for the four default rules);
    • [quotas] quota_firewall_group/policy/rule;
    • service_plugins must include firewall_v2.
  • CLI:
    • neutron-db-manage --subproject neutron-fwaas upgrade (migrations, entry point pyproject.toml:53);
    • neutron-status upgrade check (fails if FWaaS v1 is still configured, neutron_fwaas/cmd/upgrade_checks/checks.py:30);
    • neutron-ovn-db-sync-util --sync_plugin neutron_fwaas_nb_sync. For a Go rewrite, the sync function belongs in the controller's reconcile loop (§ 10).

7. Conformance strategy

  • Pure translation: run the 935 OVN vectors and 8 iptables vectors against the Go ACL builder. They must produce identical dicts (match string byte for byte, priority, action, direction, external_ids).
  • API:
    • replay _rewrite/exchanges_stable-2026.2.json as differential evidence: same requests, compare status, NeutronError.type and body shape, ignoring ids and timestamps;
    • validate bodies against _rewrite/response-schemas_stable-2026.2.json. The exchanges come from the original unit tests, which use DummyDriverDB (no back end, neutron_fwaas/tests/unit/services/firewall/test_fwaas_plugin_v2.py:73), so their statuses are those of the DB layer (CREATED/INACTIVE), not of the OVN driver.
  • Black-box suite: the neutron-tempest-plugin clone of the workspace has an fwaas test package (API and scenario tests; contents not inspected in this pack). Run it against the rewrite with OVN.
  • OVN end-to-end (not covered by any captured data): spin up an NB DB (ovsdb-server in a container) and compare, for a scripted sequence (create, update ports, insert, remove, disable, enable, delete), the Port_Group/ACL rows written by the original (neutron-server with the OVN driver) and by the rewrite.
  • Original fixtures, for reference: neutron_fwaas/tests/unit/services/firewall/test_fwaas_plugin_v2.py:54 (API test base), neutron_fwaas/tests/unit/services/firewall/service_drivers/ovn/test_firewall_l3_driver.py (OVN driver with a mocked NB IDL).

8. Gaps found while building the pack

  • **standard-attr-fwaas-v2 never loads**: the class in neutron_fwaas/extensions/firewall_v2_stdattrs.py:19 is named Standard_attr_fwaas, while Neutron's file-based loader expects Firewall_v2_stdattrs (neutron:neutron/api/extensions.py:459). Seen at run time while building the route table: "Did not find expected name". A rewrite can advertise it (the attributes are there) or keep it hidden for strict compatibility.
  • No route table or api-ref inside the repository: the routes came from the running pipeline and the api-ref from neutron-lib.
  • Exchanges without seeds: the HTTP tests build their data through the API itself, so the exchanges are not independently replayable.
  • Test-harness workarounds (adapter setup(), never in the repository): run in isolation, the OVN driver tests need Neutron's core options registered (setproctitle), and the iptables tests need the [fwaas] options registered.
  • Dead code:
    • FWaaSPluginApiMixin.set_firewall_status/firewall_deleted (v1 RPC);
    • FirewallAgentApi.delete_firewall_group;
    • process_services_sync;
    • the neutron_service_plugins check;
    • ProtocolNotSupported, FirewallGroupPortInvalidProject;
    • rule_count, position.
  • Misleading statuses and messages: 404 for FirewallPolicyConflict, FirewallRuleSharingConflict and MechanismDriverNotFound; "of type Firewall Group" on default policies; "protocol is set to ICMP" for any non-tcp/udp protocol.
  • **The internal callback after a policy deletion is AFTER_UPDATE** (neutron_fwaas/services/firewall/service_drivers/driver_api.py:272).
  • Protocols without OVN mapping produce an ACL that matches all IP traffic (§ 3.3). This is a security trap: a deny rule for gre drops everything, and an allow rule for gre allows everything, of that IP version, at its priority.

9. Non-functional requirements

  • Every API write is synchronous with the OVN NB DB. The NB connection must be up for any FWaaS write; reads only need the database.
  • Expected scale (default quotas): ≤ 10 groups, 10 policies and 100 rules per project.
  • Consistency with OVN must be restorable: provide a reconcile loop (the original only has the manual neutron-ovn-db-sync-util, which does not remove stale ACLs).
  • List calls have a write side effect (default group creation) that must stay idempotent under concurrency (unique key on default_firewall_groups.project_id).

10. Rewrite checklist

  • [ ] Serve the 17 operations of _rewrite/routes_stable-2026.2.json under /v2.0/fwaas/, with Neutron's generic list, filter, field, pagination and sort semantics and the NeutronError body.
  • [ ] Reproduce the attribute map (defaults, conversions such as case-insensitive action, port range strings, IP strings kept as entered) from neutron-lib:neutron_lib/api/definitions/firewall_v2.py:51.
  • [ ] Reproduce every business check of 01, with the statuses and type names of 02, including the "wrong" 404s.
  • [ ] Keep insert_rule/remove_rule responses unwrapped.
  • [ ] Implement the default firewall group: lazy creation on list and group create, the [default_fwg_rules] options, the reserved names, the restrictions on updating and deleting it.
  • [ ] Implement audited reset rules.
  • [ ] Implement the OVN translation of § 3.3 and pass the 935 vectors.
  • [ ] Implement the Port_Group lifecycle of § 3.4 with identical names and external_ids, so that an NB DB written by the original keeps working.
  • [ ] Refuse non-router ports (FirewallGroupPortNotSupported), as the OVN driver does; or decide explicitly to support VM ports (new behaviour).
  • [ ] Use the migrated schema; create the standardattributes rows; keep the unique key on port_id.
  • [ ] Load the 26 policy rules, including the :shared attribute rules and shared_firewall_*.
  • [ ] Emit firewall_*.create/update/delete.start/.end notifications if the Neutron rewrite emits them for other resources.
  • [ ] Add a reconcile loop that also removes stale ACLs and ports (improvement over the original sync).
  • [ ] Decide on the stateless-allow semantics (keep allow-stateless for compatibility) and on the priority overflow above 999 rules.
  • [ ] Not needed on OVN: RPC topics q-firewall-plugin/firewall_agent, L3/L2 agent extensions, iptables, conntrack, NFLOG logging.

Source: reverse-spec-neutron-fwaas/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.