)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"b30365e6_65dafbeb","updated":"2026-04-06 22:05:48.000000000","message":"Thanks for working on this, just an initial review. We also need to get Rodolfo\u0027s input, I\u0027ve added him.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"b87cff21594011d8e6feefa40ded1eb1b6b3e9e0","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"c792337c_888da389","updated":"2026-04-03 02:07:20.000000000","message":"recheck","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":9,"id":"4672d43c_3bcc20b5","updated":"2026-04-08 06:33:50.000000000","message":"Addressed all feedback. PS9 includes code changes for comments 1-4 and 7-8, with replies for comments 5-6 explaining existing design decisions.","commit_id":"60b51a785912334adae1a8f603022af6ad6ed9ab"},{"author":{"_account_id":16688,"name":"Rodolfo Alonso","email":"ralonsoh@redhat.com","username":"rodolfo-alonso-hernandez"},"change_message_id":"ca3988521fd96802b5097648e621ebb9c9e99295","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"ea955ca9_843e6617","updated":"2026-04-08 07:23:39.000000000","message":"I\u0027m blocking this patch because:\n1) We are migrating during this cycle to `HA_Chassis_Group` in the `Logical_Router_Ports`, instead of `Gateway_Chassis`.\n2) There is an ongoing spec and feature that will provide this functionality, not using a tailor made script only for OVN, but using the existing API and the CLI commands: https://review.opendev.org/q/topic:%22bug/2103521%22","commit_id":"60b51a785912334adae1a8f603022af6ad6ed9ab"},{"author":{"_account_id":16688,"name":"Rodolfo Alonso","email":"ralonsoh@redhat.com","username":"rodolfo-alonso-hernandez"},"change_message_id":"d199a9e0bbdce31b8a7a7db6e4adc35e5780f6e3","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"5f49957d_4c1e4caf","in_reply_to":"504c0a4f_7e84e38c","updated":"2026-04-09 06:24:03.000000000","message":"Yes, this tool could be useful, but I would propose it in D/S repositories, not in the U/S one.\n\nIn any case, we can discuss it during a team meeting or the PTG. If by any chance this is being accepted for U/S stable branches, we cannot add a reno with \"feature\" section.","commit_id":"60b51a785912334adae1a8f603022af6ad6ed9ab"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"7145d380460da769788d668434dbdd341dc2a387","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"504c0a4f_7e84e38c","in_reply_to":"ae292926_3268888e","updated":"2026-04-08 13:43:35.000000000","message":"Is this something we should consider for stable branches? I realize it\u0027s a feature but it\u0027s self-contained in the one script.","commit_id":"60b51a785912334adae1a8f603022af6ad6ed9ab"},{"author":{"_account_id":16688,"name":"Rodolfo Alonso","email":"ralonsoh@redhat.com","username":"rodolfo-alonso-hernandez"},"change_message_id":"a54cb1faa2aa815c0923239279343092b48d6b6f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"ae292926_3268888e","in_reply_to":"ea955ca9_843e6617","updated":"2026-04-08 07:27:36.000000000","message":"The feature that is migrating the usage of `Gateway_Chassis` to `HA_Chassis_Group`: https://review.opendev.org/q/topic:%22bug/2092271%22","commit_id":"60b51a785912334adae1a8f603022af6ad6ed9ab"}],"doc/source/admin/ovn/l3_scheduler.rst":[{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":190,"context_line":"* A chassis liveness check excludes unresponsive chassis from scheduling"},{"line_number":191,"context_line":"  candidates, preventing assignments to zombie nodes."},{"line_number":192,"context_line":""},{"line_number":193,"context_line":"This feature was implemented for [6]_."},{"line_number":194,"context_line":""},{"line_number":195,"context_line":""},{"line_number":196,"context_line":"References"}],"source_content_type":"text/x-rst","patch_set":8,"id":"af8509b6_baddd72c","line":193,"updated":"2026-04-06 22:05:48.000000000","message":"maybe change to \u0027in\u0027 like the others, then update below to be this change","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":190,"context_line":"* A chassis liveness check excludes unresponsive chassis from scheduling"},{"line_number":191,"context_line":"  candidates, preventing assignments to zombie nodes."},{"line_number":192,"context_line":""},{"line_number":193,"context_line":"This feature was implemented for [6]_."},{"line_number":194,"context_line":""},{"line_number":195,"context_line":""},{"line_number":196,"context_line":"References"}],"source_content_type":"text/x-rst","patch_set":8,"id":"be4f1ee6_7b1f26ee","line":193,"in_reply_to":"af8509b6_baddd72c","updated":"2026-04-08 06:33:50.000000000","message":"Done, changed \"for\" to \"in\" to match the other references, and updated 6 to point to this change.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"}],"neutron/cmd/rebalance_ovn_gateway.py":[{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":30,"context_line":""},{"line_number":31,"context_line":"    # Dry-run with plan file — review before applying"},{"line_number":32,"context_line":"    neutron-rebalance-ovn-gateway --config-file /etc/neutron/neutron.conf \\\\"},{"line_number":33,"context_line":"        --plan-file /tmp/rebalance-plan.json"},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"    # Apply a previously reviewed plan file"},{"line_number":36,"context_line":"    neutron-rebalance-ovn-gateway --config-file /etc/neutron/neutron.conf \\\\"}],"source_content_type":"text/x-python","patch_set":8,"id":"05f76ebd_892ad599","line":33,"updated":"2026-04-06 22:05:48.000000000","message":"Ok, so with out --plan-file just written to stdout? And will a subsequent run with the same args over-write an existing file or complain?","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":30,"context_line":""},{"line_number":31,"context_line":"    # Dry-run with plan file — review before applying"},{"line_number":32,"context_line":"    neutron-rebalance-ovn-gateway --config-file /etc/neutron/neutron.conf \\\\"},{"line_number":33,"context_line":"        --plan-file /tmp/rebalance-plan.json"},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"    # Apply a previously reviewed plan file"},{"line_number":36,"context_line":"    neutron-rebalance-ovn-gateway --config-file /etc/neutron/neutron.conf \\\\"}],"source_content_type":"text/x-python","patch_set":8,"id":"02a730d2_c3d62c30","line":33,"in_reply_to":"05f76ebd_892ad599","updated":"2026-04-08 06:33:50.000000000","message":"Without --plan-file, the full plan is now dumped as JSON to the console at the end of the dry-run (in addition to the per-LRP log lines). With --plan-file, the file is silently overwritten if it already exists, matching terraform plan -out\u003d conventions. Updated the help text to make both behaviors explicit.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":71,"context_line":"Reference: Launchpad RFE #2096704 (approved by Neutron Drivers 2025-01-31)"},{"line_number":72,"context_line":"\"\"\""},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"from __future__ import annotations"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":"from collections import defaultdict"},{"line_number":77,"context_line":"import math"}],"source_content_type":"text/x-python","patch_set":8,"id":"3b8aee7e_52732824","line":74,"updated":"2026-04-06 22:05:48.000000000","message":"not used? But pep8 job didn\u0027t complain","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":71,"context_line":"Reference: Launchpad RFE #2096704 (approved by Neutron Drivers 2025-01-31)"},{"line_number":72,"context_line":"\"\"\""},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"from __future__ import annotations"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":"from collections import defaultdict"},{"line_number":77,"context_line":"import math"}],"source_content_type":"text/x-python","patch_set":8,"id":"e2e9ae8d_cb51b93c","line":74,"in_reply_to":"3b8aee7e_52732824","updated":"2026-04-08 06:33:50.000000000","message":"Good catch, removed. There are no type annotations in the file, and pep8 does not flag unused future imports.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":110,"context_line":"    cfg.BoolOpt("},{"line_number":111,"context_line":"        \u0027apply\u0027,"},{"line_number":112,"context_line":"        default\u003dFalse,"},{"line_number":113,"context_line":"        help\u003d\u0027Apply changes. Without this flag, runs in dry-run mode.\u0027),"},{"line_number":114,"context_line":"    cfg.FloatOpt("},{"line_number":115,"context_line":"        \u0027commit_interval\u0027,"},{"line_number":116,"context_line":"        default\u003d10.0,"}],"source_content_type":"text/x-python","patch_set":8,"id":"20be3568_3912a841","line":113,"updated":"2026-04-06 22:05:48.000000000","message":"Nit: I like the dry-run option, but can you make it so a user can also pass --dry-run ? Just feel it would make people trust the command line a bit. I think they can both just modify the same variable. If it\u0027s not that simple let me know.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":110,"context_line":"    cfg.BoolOpt("},{"line_number":111,"context_line":"        \u0027apply\u0027,"},{"line_number":112,"context_line":"        default\u003dFalse,"},{"line_number":113,"context_line":"        help\u003d\u0027Apply changes. Without this flag, runs in dry-run mode.\u0027),"},{"line_number":114,"context_line":"    cfg.FloatOpt("},{"line_number":115,"context_line":"        \u0027commit_interval\u0027,"},{"line_number":116,"context_line":"        default\u003d10.0,"}],"source_content_type":"text/x-python","patch_set":8,"id":"1ff0a097_8d642a14","line":113,"in_reply_to":"20be3568_3912a841","updated":"2026-04-08 06:33:50.000000000","message":"Added --dry-run option. If both --apply and --dry-run are specified, --dry-run takes priority for safety, with a warning logged.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":140,"context_line":"             \u0027In dry-run mode, the plan is written to this file for review. \u0027"},{"line_number":141,"context_line":"             \u0027In apply mode, if provided, the plan is read from this file \u0027"},{"line_number":142,"context_line":"             \u0027and applied as-is (the scheduler is not re-invoked). \u0027"},{"line_number":143,"context_line":"             \u0027This enables a review-then-apply workflow.\u0027),"},{"line_number":144,"context_line":"    cfg.IntOpt("},{"line_number":145,"context_line":"        \u0027liveness_tolerance\u0027,"},{"line_number":146,"context_line":"        default\u003d120,"}],"source_content_type":"text/x-python","patch_set":8,"id":"d15bcd4e_f098c9d5","line":143,"updated":"2026-04-06 22:05:48.000000000","message":"So I guess it\u0027s possible to modify the plan file in-between? Can it happen then, or in some other circumstance, that the plan file will fail to apply? For example if a gateway is removed, etc. Looking below I see it does catch the exception.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":140,"context_line":"             \u0027In dry-run mode, the plan is written to this file for review. \u0027"},{"line_number":141,"context_line":"             \u0027In apply mode, if provided, the plan is read from this file \u0027"},{"line_number":142,"context_line":"             \u0027and applied as-is (the scheduler is not re-invoked). \u0027"},{"line_number":143,"context_line":"             \u0027This enables a review-then-apply workflow.\u0027),"},{"line_number":144,"context_line":"    cfg.IntOpt("},{"line_number":145,"context_line":"        \u0027liveness_tolerance\u0027,"},{"line_number":146,"context_line":"        default\u003d120,"}],"source_content_type":"text/x-python","patch_set":8,"id":"29affb9d_df328e10","line":143,"in_reply_to":"d15bcd4e_f098c9d5","updated":"2026-04-08 06:33:50.000000000","message":"Yes, the plan file can go stale between generation and apply. apply_plan() handles this in two ways: (1) each LRP\u0027s current binding is re-read and compared to what the plan recorded (line 534); if the state drifted (e.g., neutron rescheduled it), that LRP is skipped with a warning. (2) If the LRP was removed or the OVSDB call fails for any reason, the exception is caught at line 549 and counted as failed. The summary at the end reports committed/skipped/failed counts so the operator knows exactly what happened.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":277,"context_line":"            models_v2.Port.device_id \u003d\u003d l3_models.Router.id"},{"line_number":278,"context_line":"        ).filter("},{"line_number":279,"context_line":"            l3_models.Router.flavor_id.is_(None)"},{"line_number":280,"context_line":"        ).all()"},{"line_number":281,"context_line":""},{"line_number":282,"context_line":"        for port in gw_ports:"},{"line_number":283,"context_line":"            physnet \u003d net_physnets.get(port.network_id)"}],"source_content_type":"text/x-python","patch_set":8,"id":"18d19fed_1c237f71","line":280,"updated":"2026-04-06 22:05:48.000000000","message":"I\u0027m curious if there is a way to do this with a plugin get_ports() call, just to reduce the amount of DB knowledge this script has. I know there are other things in the cmd directory that do this just thinking out loud.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":277,"context_line":"            models_v2.Port.device_id \u003d\u003d l3_models.Router.id"},{"line_number":278,"context_line":"        ).filter("},{"line_number":279,"context_line":"            l3_models.Router.flavor_id.is_(None)"},{"line_number":280,"context_line":"        ).all()"},{"line_number":281,"context_line":""},{"line_number":282,"context_line":"        for port in gw_ports:"},{"line_number":283,"context_line":"            physnet \u003d net_physnets.get(port.network_id)"}],"source_content_type":"text/x-python","patch_set":8,"id":"2247a93a_8aa2f1ae","line":280,"in_reply_to":"18d19fed_1c237f71","updated":"2026-04-08 06:33:50.000000000","message":"Good question. The plugin get_ports() (via manager.init() + directory.get_plugin()) would reduce DB schema coupling, but requires bootstrapping the full ML2 plugin stack. As neutron_ovn_db_sync_util.py shows, this needs custom subclasses to disable RPC workers, event subscriptions, hash ring management, etc. The OVO Port.get_objects() layer was also considered (does not need the plugin), but it cannot express the Router.flavor_id IS NULL join we need to filter to OVN provider routers, or the NetworkSegment physnet lookup. The direct query approach keeps the tool lightweight and matches the pattern used by most CLI tools in neutron/cmd/ (e.g., upgrade_checks/checks.py, sanitize_port_mac_addresses.py).","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":437,"context_line":"        LOG.info(\u0027%sPriority %d:\u0027, prefix, priority)"},{"line_number":438,"context_line":"        for chassis in sorted(all_gw_chassis):"},{"line_number":439,"context_line":"            count \u003d len(dist[chassis].get(priority, []))"},{"line_number":440,"context_line":"            chart \u003d \u0027█\u0027 * count"},{"line_number":441,"context_line":"            LOG.info(\u0027  %-30s %3d  %s\u0027, chassis, count, chart)"},{"line_number":442,"context_line":""},{"line_number":443,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"94c1ffee_242c7e5c","line":440,"updated":"2026-04-06 22:05:48.000000000","message":"What is the character here? I just see a blue rectangle, don\u0027t want to add anything that can be possibly be hard to translate.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":437,"context_line":"        LOG.info(\u0027%sPriority %d:\u0027, prefix, priority)"},{"line_number":438,"context_line":"        for chassis in sorted(all_gw_chassis):"},{"line_number":439,"context_line":"            count \u003d len(dist[chassis].get(priority, []))"},{"line_number":440,"context_line":"            chart \u003d \u0027█\u0027 * count"},{"line_number":441,"context_line":"            LOG.info(\u0027  %-30s %3d  %s\u0027, chassis, count, chart)"},{"line_number":442,"context_line":""},{"line_number":443,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"0858467e_b62d7c66","line":440,"in_reply_to":"94c1ffee_242c7e5c","updated":"2026-04-08 06:33:50.000000000","message":"Good catch, that was U+2588 (full block). Replaced with # to avoid encoding and rendering issues.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":1131,"name":"Brian Haley","email":"haleyb.dev@gmail.com","username":"brian-haley"},"change_message_id":"e8b0b866ffcd93ddae29d532e718274c696c4376","unresolved":true,"context_lines":[{"line_number":580,"context_line":""},{"line_number":581,"context_line":"    These checks narrow but do not eliminate the race window with"},{"line_number":582,"context_line":"    neutron-server\u0027s reactive handlers — there is no cross-process"},{"line_number":583,"context_line":"    coordination mechanism. See C-17 in the test plan."},{"line_number":584,"context_line":""},{"line_number":585,"context_line":"    Returns (current_chassis, new_chassis) or None if skipped."},{"line_number":586,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":8,"id":"2837b121_21e91485","line":583,"updated":"2026-04-06 22:05:48.000000000","message":"Ok, so there is still the possibility there could be a race. But I would assume we\u0027d recommend stopping neutron-api while running this?","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"},{"author":{"_account_id":38996,"name":"Daniel Ochoa","display_name":"Daniel Ochoa","email":"dochoa3@gmail.com","username":"danochoa"},"change_message_id":"bd958ae05e5c1897a64b7768c6dea58e04816350","unresolved":true,"context_lines":[{"line_number":580,"context_line":""},{"line_number":581,"context_line":"    These checks narrow but do not eliminate the race window with"},{"line_number":582,"context_line":"    neutron-server\u0027s reactive handlers — there is no cross-process"},{"line_number":583,"context_line":"    coordination mechanism. See C-17 in the test plan."},{"line_number":584,"context_line":""},{"line_number":585,"context_line":"    Returns (current_chassis, new_chassis) or None if skipped."},{"line_number":586,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":8,"id":"e912db7f_e263b36b","line":583,"in_reply_to":"2837b121_21e91485","updated":"2026-04-08 06:33:50.000000000","message":"Added compare-and-swap protection using OVSDB Row.verify(), following the same pattern as CheckRevisionNumberCommand. The VerifyLrpGatewayChassis command runs inside run_idl() on the connection thread: it compares the current gateway_chassis binding against the expected value, then calls verify() to lock the column through commit. If a concurrent change occurred (e.g., from neutron-server), the transaction aborts and the LRP is counted as failed. This eliminates the race window between our pre-commit checks and the actual OVSDB commit. Stopping neutron-server is not required.","commit_id":"05909aaadf9e2094d930ac73600f84820c20db37"}]}
