)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"fe7c8848d963eb1e5f5600bc77ffb3abe9e898bb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"780cecf6_f82383ba","updated":"2026-09-22 14:39:46.000000000","message":"I think the patch is on the right direction but I still think it can be simpler.","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":16688,"name":"Rodolfo Alonso","email":"ralonsoh@redhat.com","username":"rodolfo-alonso-hernandez"},"change_message_id":"a23985d4405543cb19ed71760383f16c9d7175b3","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"08b7b72f_1f2fa54b","updated":"2026-09-29 07:05:36.000000000","message":"-1 for the questions:\n1) For each VFR, should we have a single thread handling any operation of this router? In other words, this implementation allows parallel command execution. Is that something that we should allow.\n2) IMO (but that\u0027s a design option), I would make the executor to implement the commands, via object methods, avoiding to create a new object (the command) and pass it to the executor. But this is a nit.","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"80df08c3bd412b1f42dc779da26d9c8f317f0b8f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"1c78e92b_1aae39a9","updated":"2026-09-28 20:22:53.000000000","message":"check experimental","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"51e83b5b0d2cb3615b9c8e181df7862265b2d0db","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":9,"id":"20463c3b_78ec6588","in_reply_to":"08b7b72f_1f2fa54b","updated":"2026-09-30 01:38:52.000000000","message":"1) Not sure if I understand your question. Currently, there is a single executor which starts a new thread for each VRF. For example, if you had FSM send create 3 VRFs you would have three threads. So I think I have what you are suggesting, but maybe I am not understanding your correctly\n\n2) Actually Kuba had similar comment(privately over slack) and I had it mocked up locally. I will be pushing that change soon","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"a14bd411df574b6a9417af26ae6cd85ffb218cb7","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":9,"id":"52ab3d18_929761a1","in_reply_to":"20463c3b_78ec6588","updated":"2026-09-30 02:11:16.000000000","message":"2) was addressed in patchset 10+","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"76b578a8_6d2e9eb5","in_reply_to":"52ab3d18_929761a1","updated":"2026-10-09 14:49:30.000000000","message":"Acknowledged","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"0ce4205eab0888aaf396a6f9e883674d0e2c693c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":11,"id":"3495c6b0_a91690fb","updated":"2026-10-01 19:24:18.000000000","message":"-1 I am investigating if frr_driver is thread safe. Current approach of multiple threads might not work properly.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"1715a4b7850b7156d0043c4c739ceba159ffe8f6","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":16,"id":"eb1df808_a8c35a8c","updated":"2026-10-16 00:28:51.000000000","message":"check experimental","commit_id":"bd73f8f5fdb0c108e19a7d84090e256b4b75b687"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"0d8db91f10aa7eac33cda1d151cec6f81e3e7dfc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":17,"id":"cda20e4e_f7e1e2b7","updated":"2026-10-18 17:09:54.000000000","message":"It\u0027s a soft -1 to discuss the keys for two maps we wanted to have in sync.","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"6beac085a9d212f477acebfc8e65293d859b24f8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":18,"id":"e7e7b1c3_328ca76a","updated":"2026-10-19 18:15:33.000000000","message":"check experimental","commit_id":"b3c81323b598505dbd11ce044538085e482bb8c0"}],"neutron/agent/ovn/extensions/evpn/__init__.py":[{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"d8b741593913bb2e11fc1e26e69185ad2836abc2","unresolved":true,"context_lines":[{"line_number":101,"context_line":"        driver \u003d fsm_frr_driver.FsmFrrVtyshDriver("},{"line_number":102,"context_line":"            peer_interface\u003dCONF.ovn_evpn.bgp_local_interface,"},{"line_number":103,"context_line":"            bgp_router_id\u003dself.cfg.local_ip)"},{"line_number":104,"context_line":"        executor \u003d evpn_executor.EvpnExecutor("},{"line_number":105,"context_line":"            self.svd, self.cfg.br_mtu, driver)"},{"line_number":106,"context_line":"        self._evpn_fsm.set_provision_executor(executor)"},{"line_number":107,"context_line":"        vrf_handler \u003d netlink_monitor.VrfHandler(self._evpn_fsm)"}],"source_content_type":"text/x-python","patch_set":12,"id":"c7fc3382_0e11aeb8","line":104,"updated":"2026-10-07 20:28:42.000000000","message":"No action required: Why is it created here? Wouldn\u0027t it be better to have it encapsulated in the FSM itself as part of the `setup()` call? The plugin itself doesn\u0027t care much there is some executor existence.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"0bbf0644b8053091e09bf074c5280c850612fc30","unresolved":true,"context_lines":[{"line_number":101,"context_line":"        driver \u003d fsm_frr_driver.FsmFrrVtyshDriver("},{"line_number":102,"context_line":"            peer_interface\u003dCONF.ovn_evpn.bgp_local_interface,"},{"line_number":103,"context_line":"            bgp_router_id\u003dself.cfg.local_ip)"},{"line_number":104,"context_line":"        executor \u003d evpn_executor.EvpnExecutor("},{"line_number":105,"context_line":"            self.svd, self.cfg.br_mtu, driver)"},{"line_number":106,"context_line":"        self._evpn_fsm.set_provision_executor(executor)"},{"line_number":107,"context_line":"        vrf_handler \u003d netlink_monitor.VrfHandler(self._evpn_fsm)"}],"source_content_type":"text/x-python","patch_set":12,"id":"962141ce_21f55bba","line":104,"in_reply_to":"7992736a_86390071","updated":"2026-10-08 21:30:27.000000000","message":"I think the similar approach would be passing the driver as a parameter to the `setup()` method then and remove the `executor` setter. It feels a bit weird having an external setter for an internal thing. But as I said before, no-action-required :) I just thought it would make sense to encapsulate the executor fully in the FSM.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[{"line_number":101,"context_line":"        driver \u003d fsm_frr_driver.FsmFrrVtyshDriver("},{"line_number":102,"context_line":"            peer_interface\u003dCONF.ovn_evpn.bgp_local_interface,"},{"line_number":103,"context_line":"            bgp_router_id\u003dself.cfg.local_ip)"},{"line_number":104,"context_line":"        executor \u003d evpn_executor.EvpnExecutor("},{"line_number":105,"context_line":"            self.svd, self.cfg.br_mtu, driver)"},{"line_number":106,"context_line":"        self._evpn_fsm.set_provision_executor(executor)"},{"line_number":107,"context_line":"        vrf_handler \u003d netlink_monitor.VrfHandler(self._evpn_fsm)"}],"source_content_type":"text/x-python","patch_set":12,"id":"b4393131_8c92e8eb","line":104,"in_reply_to":"962141ce_21f55bba","updated":"2026-10-09 14:49:30.000000000","message":"Done","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":true,"context_lines":[{"line_number":101,"context_line":"        driver \u003d fsm_frr_driver.FsmFrrVtyshDriver("},{"line_number":102,"context_line":"            peer_interface\u003dCONF.ovn_evpn.bgp_local_interface,"},{"line_number":103,"context_line":"            bgp_router_id\u003dself.cfg.local_ip)"},{"line_number":104,"context_line":"        executor \u003d evpn_executor.EvpnExecutor("},{"line_number":105,"context_line":"            self.svd, self.cfg.br_mtu, driver)"},{"line_number":106,"context_line":"        self._evpn_fsm.set_provision_executor(executor)"},{"line_number":107,"context_line":"        vrf_handler \u003d netlink_monitor.VrfHandler(self._evpn_fsm)"}],"source_content_type":"text/x-python","patch_set":12,"id":"7992736a_86390071","line":104,"in_reply_to":"c7fc3382_0e11aeb8","updated":"2026-10-08 21:12:04.000000000","message":"I am looking at it again. I see your point, why have `executor` object in the plugin since it is not really used. But you could make the same argument about the existing `driver` object.\nIf I go with the previous implementation then I am just passing variables into FSM that will just be passed into the executor. \nWould something like this be a middle ground solution?\n```\nself._evpn_fsm.set_provision_executor(\n   EvpnExecutor(self.svd, self.cfg.br_mtu, driver))\n```\nnow you dont have `executor` object.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"}],"neutron/agent/ovn/extensions/evpn/executor.py":[{"author":{"_account_id":16688,"name":"Rodolfo Alonso","email":"ralonsoh@redhat.com","username":"rodolfo-alonso-hernandez"},"change_message_id":"a23985d4405543cb19ed71760383f16c9d7175b3","unresolved":true,"context_lines":[{"line_number":29,"context_line":"    cleaned up automatically after ``_run`` completes via identity check."},{"line_number":30,"context_line":"    \"\"\""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"    def __init__(self, max_retries, initial_backoff, max_backoff):"},{"line_number":33,"context_line":"        self._max_retries \u003d max_retries"},{"line_number":34,"context_line":"        self._initial_backoff \u003d initial_backoff"},{"line_number":35,"context_line":"        self._max_backoff \u003d max_backoff"}],"source_content_type":"text/x-python","patch_set":9,"id":"9c0d5362_e0184be4","line":32,"range":{"start_line":32,"start_character":23,"end_line":32,"end_character":64},"updated":"2026-09-29 07:05:36.000000000","message":"nit: I think we can assign the default values here. The instantiation of this class object, will require no parameters.\n```\nself._executor \u003d evpn_executor.Executor()\n```","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"a14bd411df574b6a9417af26ae6cd85ffb218cb7","unresolved":false,"context_lines":[{"line_number":29,"context_line":"    cleaned up automatically after ``_run`` completes via identity check."},{"line_number":30,"context_line":"    \"\"\""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"    def __init__(self, max_retries, initial_backoff, max_backoff):"},{"line_number":33,"context_line":"        self._max_retries \u003d max_retries"},{"line_number":34,"context_line":"        self._initial_backoff \u003d initial_backoff"},{"line_number":35,"context_line":"        self._max_backoff \u003d max_backoff"}],"source_content_type":"text/x-python","patch_set":9,"id":"c21b17e6_c62b513c","line":32,"range":{"start_line":32,"start_character":23,"end_line":32,"end_character":64},"in_reply_to":"9c0d5362_e0184be4","updated":"2026-09-30 02:11:16.000000000","message":"Done","commit_id":"91033b571075bc7ae82c4e114fc3b8104e3de952"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"812f7ab06350446d98ddd336f4ad3067d7b4e8da","unresolved":true,"context_lines":[{"line_number":50,"context_line":"        self._br_mtu \u003d br_mtu"},{"line_number":51,"context_line":"        self._driver \u003d driver"},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"    def provision(self, evpn):"},{"line_number":54,"context_line":"        \"\"\"Cancel in-flight work for *evpn* and provision in background.\"\"\""},{"line_number":55,"context_line":"        self._submit(evpn, self._do_provision, (evpn,))"},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"41ad7dae_f7a9261e","line":53,"range":{"start_line":53,"start_character":24,"end_line":53,"end_character":28},"updated":"2026-10-01 18:03:39.000000000","message":"If we pass live `EvpnInstance` objects and start using them in other threads, won\u0027t other FSM operations be able to modify them? E.g. during a `_do_provision()` call, with a thread switch between `add_vni()` and `create_router()`, if we get a PORT_BINDING_DELETE and `_unadvertise_and_clear_pb()` clears mac/vni/vid, which then get passed as None to create_router() in `_do_provision()`. I think we should pass the individual args like you do in `deprovision()`.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":false,"context_lines":[{"line_number":50,"context_line":"        self._br_mtu \u003d br_mtu"},{"line_number":51,"context_line":"        self._driver \u003d driver"},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"    def provision(self, evpn):"},{"line_number":54,"context_line":"        \"\"\"Cancel in-flight work for *evpn* and provision in background.\"\"\""},{"line_number":55,"context_line":"        self._submit(evpn, self._do_provision, (evpn,))"},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"bc660bef_b3f6ff44","line":53,"range":{"start_line":53,"start_character":24,"end_line":53,"end_character":28},"in_reply_to":"41ad7dae_f7a9261e","updated":"2026-10-08 21:12:04.000000000","message":"Done","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"812f7ab06350446d98ddd336f4ad3067d7b4e8da","unresolved":true,"context_lines":[{"line_number":75,"context_line":"    def _submit(self, evpn, action, args):"},{"line_number":76,"context_line":"        \"\"\"Cancel in-flight work for *evpn* and start *action* in bg.\"\"\""},{"line_number":77,"context_line":"        with self._active_instances_lock:"},{"line_number":78,"context_line":"            active_entry \u003d self._active_instances.get(evpn)"},{"line_number":79,"context_line":"            predecessor \u003d None"},{"line_number":80,"context_line":"            if active_entry:"},{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"}],"source_content_type":"text/x-python","patch_set":11,"id":"bc32b9e3_316b1244","line":78,"updated":"2026-10-01 18:03:39.000000000","message":"Keying off of instance means that a create/delete/recreate where the vrf is the same end up not executing serially. If the deprovision is still outstanding, the recreate will be able to fire separately. If it was keyed off of the vrf instead of the instance object, they would be serialized. The FSM \u0027instances\u0027 is keyed off of VRF I assume for a similar reason.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[{"line_number":75,"context_line":"    def _submit(self, evpn, action, args):"},{"line_number":76,"context_line":"        \"\"\"Cancel in-flight work for *evpn* and start *action* in bg.\"\"\""},{"line_number":77,"context_line":"        with self._active_instances_lock:"},{"line_number":78,"context_line":"            active_entry \u003d self._active_instances.get(evpn)"},{"line_number":79,"context_line":"            predecessor \u003d None"},{"line_number":80,"context_line":"            if active_entry:"},{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"}],"source_content_type":"text/x-python","patch_set":11,"id":"a5a072bb_25882ab1","line":78,"in_reply_to":"047fcecd_7223c3e5","updated":"2026-10-09 14:49:30.000000000","message":"Done","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"0ce4205eab0888aaf396a6f9e883674d0e2c693c","unresolved":true,"context_lines":[{"line_number":75,"context_line":"    def _submit(self, evpn, action, args):"},{"line_number":76,"context_line":"        \"\"\"Cancel in-flight work for *evpn* and start *action* in bg.\"\"\""},{"line_number":77,"context_line":"        with self._active_instances_lock:"},{"line_number":78,"context_line":"            active_entry \u003d self._active_instances.get(evpn)"},{"line_number":79,"context_line":"            predecessor \u003d None"},{"line_number":80,"context_line":"            if active_entry:"},{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"}],"source_content_type":"text/x-python","patch_set":11,"id":"047fcecd_7223c3e5","line":78,"in_reply_to":"bc32b9e3_316b1244","updated":"2026-10-01 19:24:18.000000000","message":"Commands for the specific EVPN instance will be serialized. Notice, id we have an `active_entry` it signals cancel and then send the current thread`predecessor` to the new thread. The first thing the new thread does it calls `join` on the `predecessor`. So commands are serialized per EVPNInstance.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"812f7ab06350446d98ddd336f4ad3067d7b4e8da","unresolved":true,"context_lines":[{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"},{"line_number":82,"context_line":"                predecessor \u003d active_entry[1]"},{"line_number":83,"context_line":"            cancel_event \u003d threading.Event()"},{"line_number":84,"context_line":"            thread \u003d threading.Thread("},{"line_number":85,"context_line":"                target\u003dself._run,"},{"line_number":86,"context_line":"                args\u003d(evpn, action, args, cancel_event, predecessor),"},{"line_number":87,"context_line":"                daemon\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":11,"id":"004efdc0_0be7c41f","line":84,"updated":"2026-10-01 18:03:39.000000000","message":"From Codex:\n\nneutron/agent/ovn/extensions/evpn/executor.py:84 permits different instances to run simultaneously, but the shared `FrrVtyshDriver` has no synchronization. Its check-then-apply sequence and `write memory` calls can overlap. The previous FSM lock serialized all provisioning side effects. Add backend-level serialization or demonstrate that both backends are thread-safe.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"0ce4205eab0888aaf396a6f9e883674d0e2c693c","unresolved":true,"context_lines":[{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"},{"line_number":82,"context_line":"                predecessor \u003d active_entry[1]"},{"line_number":83,"context_line":"            cancel_event \u003d threading.Event()"},{"line_number":84,"context_line":"            thread \u003d threading.Thread("},{"line_number":85,"context_line":"                target\u003dself._run,"},{"line_number":86,"context_line":"                args\u003d(evpn, action, args, cancel_event, predecessor),"},{"line_number":87,"context_line":"                daemon\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":11,"id":"6f33d018_9be705c5","line":84,"in_reply_to":"004efdc0_0be7c41f","updated":"2026-10-01 19:24:18.000000000","message":"This is actually very good catch! Frr driver writes into single frr.conf file so having multiple parallel executions might be problematic. I will look into it asap.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"},{"line_number":82,"context_line":"                predecessor \u003d active_entry[1]"},{"line_number":83,"context_line":"            cancel_event \u003d threading.Event()"},{"line_number":84,"context_line":"            thread \u003d threading.Thread("},{"line_number":85,"context_line":"                target\u003dself._run,"},{"line_number":86,"context_line":"                args\u003d(evpn, action, args, cancel_event, predecessor),"},{"line_number":87,"context_line":"                daemon\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":11,"id":"fb326389_2ed718ef","line":84,"in_reply_to":"36807225_9de8ffb1","updated":"2026-10-09 14:49:30.000000000","message":"Done","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"8e3eff5a65173eb1bff3e7cba84efed893e9a3b0","unresolved":true,"context_lines":[{"line_number":81,"context_line":"                active_entry[0].set()  # signal active thread to cancel"},{"line_number":82,"context_line":"                predecessor \u003d active_entry[1]"},{"line_number":83,"context_line":"            cancel_event \u003d threading.Event()"},{"line_number":84,"context_line":"            thread \u003d threading.Thread("},{"line_number":85,"context_line":"                target\u003dself._run,"},{"line_number":86,"context_line":"                args\u003d(evpn, action, args, cancel_event, predecessor),"},{"line_number":87,"context_line":"                daemon\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":11,"id":"36807225_9de8ffb1","line":84,"in_reply_to":"6f33d018_9be705c5","updated":"2026-10-02 21:22:55.000000000","message":"OK check patcheset 12. I changed the executor to be a single thread where only one EVPN Instance is getting provisioned at a time. No way around it because frr_driver is not thread safe","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"812f7ab06350446d98ddd336f4ad3067d7b4e8da","unresolved":true,"context_lines":[{"line_number":113,"context_line":"            self._cleanup(evpn, cancel_event)"},{"line_number":114,"context_line":"            return"},{"line_number":115,"context_line":"        LOG.error(\"Exhausted %d retries\", self._max_retries)"},{"line_number":116,"context_line":"        self._cleanup(evpn, cancel_event)"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def _cleanup(self, evpn, cancel_event):"},{"line_number":119,"context_line":"        \"\"\"Remove entry only if we are still the current one.\"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"d7ae26ea_82f2ae13","line":116,"updated":"2026-10-01 18:03:39.000000000","message":"The threading executor, with the existing states, seems to inherently mean that the FSM can be left in a wrong state. The callback doesn\u0027t raise, so it seems like we would happily transition to ADVERTISING even though we never actually reach a state where things are advertised. Whereas a synchronous attempt would catch the callback exception and never transition to ADVERTISING.\n\nIt seems like there should at least be an ERROR state that is a valid FSM transition that we could signal a transition to in this case.","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[{"line_number":113,"context_line":"            self._cleanup(evpn, cancel_event)"},{"line_number":114,"context_line":"            return"},{"line_number":115,"context_line":"        LOG.error(\"Exhausted %d retries\", self._max_retries)"},{"line_number":116,"context_line":"        self._cleanup(evpn, cancel_event)"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def _cleanup(self, evpn, cancel_event):"},{"line_number":119,"context_line":"        \"\"\"Remove entry only if we are still the current one.\"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"02e151bd_04fbb981","line":116,"in_reply_to":"5cffbcf5_e8bc021a","updated":"2026-10-09 14:49:30.000000000","message":"Done","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"0ce4205eab0888aaf396a6f9e883674d0e2c693c","unresolved":true,"context_lines":[{"line_number":113,"context_line":"            self._cleanup(evpn, cancel_event)"},{"line_number":114,"context_line":"            return"},{"line_number":115,"context_line":"        LOG.error(\"Exhausted %d retries\", self._max_retries)"},{"line_number":116,"context_line":"        self._cleanup(evpn, cancel_event)"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def _cleanup(self, evpn, cancel_event):"},{"line_number":119,"context_line":"        \"\"\"Remove entry only if we are still the current one.\"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"5cffbcf5_e8bc021a","line":116,"in_reply_to":"d7ae26ea_82f2ae13","updated":"2026-10-01 19:24:18.000000000","message":"O boy you should have been in the room when Kuba and I discussed this :) I am also not big fan of transitioning into ADVERTISING if you are actually not advertising any routes yet(i.e. SVD configured correctly, frr evpn router up). Previous version of this patch actually had lot more granular states [1] but it made the whole FSM more complicated. Specifically with that approach, the executor would have to signal states back to FSM. I.e. provision retry exhausted, retrying etc. With this approach is more like \"send it to executor and let the executor handle everything\" from FSM prospective. The current implementation is simpler.\nIf it is just matter of confusing name. I could rename ADVERTISING to WAITING. If you think about it, that is what you do in that state, it is either waiting for some create events or some delete events. \n\n[1] https://review.opendev.org/c/openstack/neutron/+/1006431/comment/607fec02_940cb1db/","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"d8b741593913bb2e11fc1e26e69185ad2836abc2","unresolved":true,"context_lines":[{"line_number":34,"context_line":"    retry_after: float \u003d 0.0"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"class EvpnExecutor:"},{"line_number":38,"context_line":"    \"\"\"Single-threaded EVPN provisioning executor with dedup queue."},{"line_number":39,"context_line":""},{"line_number":40,"context_line":"    Public methods ``provision(evpn)`` and ``deprovision(evpn)`` are"}],"source_content_type":"text/x-python","patch_set":12,"id":"47e43e96_b84166cb","line":37,"updated":"2026-10-07 20:28:42.000000000","message":"A unittests mocking out the `_do_*` methods would be useful.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":false,"context_lines":[{"line_number":34,"context_line":"    retry_after: float \u003d 0.0"},{"line_number":35,"context_line":""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"class EvpnExecutor:"},{"line_number":38,"context_line":"    \"\"\"Single-threaded EVPN provisioning executor with dedup queue."},{"line_number":39,"context_line":""},{"line_number":40,"context_line":"    Public methods ``provision(evpn)`` and ``deprovision(evpn)`` are"}],"source_content_type":"text/x-python","patch_set":12,"id":"864d92c9_4540b18a","line":37,"in_reply_to":"47e43e96_b84166cb","updated":"2026-10-08 21:12:04.000000000","message":"ACK, I also moved `TestEvpnExecutor` to its own `test_executor.py`","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"d8b741593913bb2e11fc1e26e69185ad2836abc2","unresolved":true,"context_lines":[{"line_number":73,"context_line":"        Captures vrf/vni/vid/mac eagerly so concurrent FSM mutations"},{"line_number":74,"context_line":"        cannot affect the worker thread."},{"line_number":75,"context_line":"        \"\"\""},{"line_number":76,"context_line":"        vrf, vni, vid, mac \u003d evpn.vrf, evpn.vni, evpn.vid, evpn.mac"},{"line_number":77,"context_line":"        self._submit(evpn, self._do_provision, (vrf, vni, vid, mac))"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def deprovision(self, evpn):"}],"source_content_type":"text/x-python","patch_set":12,"id":"2c45d753_a0c6aab5","line":76,"updated":"2026-10-07 20:28:42.000000000","message":"nit: Do we need to decompose the evpn object? What if we just always pass down the evpn instance and the callable, omitting the `args` everywhere and use the evpn only. Then each `_do_` action can pick whatever data it needs.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":true,"context_lines":[{"line_number":73,"context_line":"        Captures vrf/vni/vid/mac eagerly so concurrent FSM mutations"},{"line_number":74,"context_line":"        cannot affect the worker thread."},{"line_number":75,"context_line":"        \"\"\""},{"line_number":76,"context_line":"        vrf, vni, vid, mac \u003d evpn.vrf, evpn.vni, evpn.vid, evpn.mac"},{"line_number":77,"context_line":"        self._submit(evpn, self._do_provision, (vrf, vni, vid, mac))"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def deprovision(self, evpn):"}],"source_content_type":"text/x-python","patch_set":12,"id":"9a3dd480_796feeee","line":76,"in_reply_to":"2c45d753_a0c6aab5","updated":"2026-10-08 21:12:04.000000000","message":"yes you could argue this is an overkill. And I could just pass `evpn` into the `_do`. \nThis mostly covers the \"in-flight\" work. Lets say `_do_provision` is running and FSM clears fields. Copy paster from fsm.py\n```\n    def _unadvertise_and_clear_pb(self, evpn):\n        self._exec.deprovision(evpn)\n        evpn.mac \u003d None\n        evpn.vni \u003d None\n        evpn.vid \u003d None\n```\nAdditionally, even for a more normal deprovision way, FSM call `deprovision` and moves on. (this is non-blocking call). Then immediately clears attributes of evpn.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":true,"context_lines":[{"line_number":73,"context_line":"        Captures vrf/vni/vid/mac eagerly so concurrent FSM mutations"},{"line_number":74,"context_line":"        cannot affect the worker thread."},{"line_number":75,"context_line":"        \"\"\""},{"line_number":76,"context_line":"        vrf, vni, vid, mac \u003d evpn.vrf, evpn.vni, evpn.vid, evpn.mac"},{"line_number":77,"context_line":"        self._submit(evpn, self._do_provision, (vrf, vni, vid, mac))"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def deprovision(self, evpn):"}],"source_content_type":"text/x-python","patch_set":12,"id":"9e04d258_8d1dc1ed","line":76,"in_reply_to":"49d5ff9f_60d9fa98","updated":"2026-10-09 14:49:30.000000000","message":"This is how I see this flow.\nfsm calls `self._exec.deprovision(evpn)` in Executor this will -\u003e create copies of vrf/vni/vid , then _submit will insert callable(`_do_*) with args(as _Task object) into the `_ready` queue. That item from the queue will execute some time later(FIFO). Now FSM moves on with `evpn.mac\u003d None, evpn.vni\u003dNone ...`.\n\nNow some later time has arrived and executor will actually execute that _Task which still has a copy of mac,vni,vrf. If the `_do_*` held just evpn instance, at provision time, it\u0027s args might be already `None`.\nRemember, FSM and Executor relation is \"send and move on\". FSM will not wait for EVPN to actually complete the full provision.","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"0bbf0644b8053091e09bf074c5280c850612fc30","unresolved":true,"context_lines":[{"line_number":73,"context_line":"        Captures vrf/vni/vid/mac eagerly so concurrent FSM mutations"},{"line_number":74,"context_line":"        cannot affect the worker thread."},{"line_number":75,"context_line":"        \"\"\""},{"line_number":76,"context_line":"        vrf, vni, vid, mac \u003d evpn.vrf, evpn.vni, evpn.vid, evpn.mac"},{"line_number":77,"context_line":"        self._submit(evpn, self._do_provision, (vrf, vni, vid, mac))"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def deprovision(self, evpn):"}],"source_content_type":"text/x-python","patch_set":12,"id":"49d5ff9f_60d9fa98","line":76,"in_reply_to":"9a3dd480_796feeee","updated":"2026-10-08 21:30:27.000000000","message":"But the call to `_submit` is still in the same thread as the `evpn.mac \u003d None`, so it\u0027s sequential and at the time we create the `_Task` object we still have the relevant data, or did I miss something?","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"628e1d9e5ae653c0727a8bc40aeb992d33913ab1","unresolved":true,"context_lines":[{"line_number":88,"context_line":"        # \"Failed to create cfgdata: vni 100 is already mapped to another"},{"line_number":89,"context_line":"        # vrf\". FRR does not support force-updating a VNI mapping; the"},{"line_number":90,"context_line":"        # stale mapping must be deleted before the VNI can be reused."},{"line_number":91,"context_line":"        # This is a known limitation and is not addressed yet."},{"line_number":92,"context_line":"        vrf, vni, vid \u003d evpn.vrf, evpn.vni, evpn.vid"},{"line_number":93,"context_line":"        self._submit(evpn, self._do_deprovision, (vrf, vni, vid))"},{"line_number":94,"context_line":""}],"source_content_type":"text/x-python","patch_set":17,"id":"d8d52b1e_72a5f1ff","line":91,"updated":"2026-10-16 16:31:50.000000000","message":"We could make this less common of an occurrence by using the RandomRangeAllocator in the future. Currently a router delete/create has a pretty high chance of creating a new VRF and re-using the VNI that was just deleted. If we allocated randomly instead, it would be very unlikely to happen in practice.","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"}],"neutron/agent/ovn/extensions/evpn/fsm.py":[{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"fe7c8848d963eb1e5f5600bc77ffb3abe9e898bb","unresolved":true,"context_lines":[{"line_number":31,"context_line":"    INIT \u003d \u0027init\u0027"},{"line_number":32,"context_line":"    WAITING_FOR_VRF \u003d \u0027waiting_for_vrf\u0027"},{"line_number":33,"context_line":"    WAITING_FOR_PORT_BINDING \u003d \u0027waiting_for_port_binding\u0027"},{"line_number":34,"context_line":"    PROVISIONING \u003d \u0027provisioning\u0027"},{"line_number":35,"context_line":"    ADVERTISING \u003d \u0027advertising\u0027"},{"line_number":36,"context_line":"    DEPROVISIONING \u003d \u0027deprovisioning\u0027"},{"line_number":37,"context_line":"    DESTROY \u003d \u0027destroy\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"1c051230_430e1670","line":34,"updated":"2026-09-22 14:39:46.000000000","message":"I still fail to understand the purpose of these two states. They both are a mid-transition states between waiting-advertising depending on the outcome of an _INTERNAL_EVENT.\n\nIf they did not exist, the FSM would functionally work still the same.\n\nFor example, comparing these two transitions always end up in the same state.\n```\nOriginal: WAITING_FOR_VRF, VRF_CREATE -\u003e ADVERTISING\n          WAITING_FOR_VRF, PB_DELETE -\u003e DESTROY\n         \nThis patch: WAITING_FOR_VRF, VRF_CREATE -\u003e PROVISIONING, DONE -\u003e ADVERTISING\n                                                       , PB_DELETE -\u003e DEPROVISION, DONE -\u003e DESTROY\n            WAITING_FOR_VRF, PB_DELETE -\u003e DESTROY                                           \n```                                                            \nBoth are equivalent.","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"a14bd411df574b6a9417af26ae6cd85ffb218cb7","unresolved":false,"context_lines":[{"line_number":31,"context_line":"    INIT \u003d \u0027init\u0027"},{"line_number":32,"context_line":"    WAITING_FOR_VRF \u003d \u0027waiting_for_vrf\u0027"},{"line_number":33,"context_line":"    WAITING_FOR_PORT_BINDING \u003d \u0027waiting_for_port_binding\u0027"},{"line_number":34,"context_line":"    PROVISIONING \u003d \u0027provisioning\u0027"},{"line_number":35,"context_line":"    ADVERTISING \u003d \u0027advertising\u0027"},{"line_number":36,"context_line":"    DEPROVISIONING \u003d \u0027deprovisioning\u0027"},{"line_number":37,"context_line":"    DESTROY \u003d \u0027destroy\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"7bf3f85c_d97887b2","line":34,"in_reply_to":"19059dec_b6bf8bb5","updated":"2026-09-30 02:11:16.000000000","message":"Done","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"3c936c6a083a7ae7351ac2e8fdcc9354168f9ef6","unresolved":true,"context_lines":[{"line_number":31,"context_line":"    INIT \u003d \u0027init\u0027"},{"line_number":32,"context_line":"    WAITING_FOR_VRF \u003d \u0027waiting_for_vrf\u0027"},{"line_number":33,"context_line":"    WAITING_FOR_PORT_BINDING \u003d \u0027waiting_for_port_binding\u0027"},{"line_number":34,"context_line":"    PROVISIONING \u003d \u0027provisioning\u0027"},{"line_number":35,"context_line":"    ADVERTISING \u003d \u0027advertising\u0027"},{"line_number":36,"context_line":"    DEPROVISIONING \u003d \u0027deprovisioning\u0027"},{"line_number":37,"context_line":"    DESTROY \u003d \u0027destroy\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"b96dce33_dc258694","line":34,"in_reply_to":"1c051230_430e1670","updated":"2026-09-23 02:04:11.000000000","message":"\u003e I still fail to understand the purpose of these two states.\n\nI think you are referring to PROVISIONING and DEPROVISIONING states, right?\n\nI think the confusion and the complexity stems from the fact that we now have a retry worker which is a separate thread that needs to be cancelable \"mid flight\".\nSo the PROVISIONING and DEPROVISIONING reflect the \"in-flight async work\". The evpn instance is not yet ADVERTISING. This makes the Transition table very explicit about the complete lifecycle of a EVPN Instance.","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"88356348af6373447fe6d5ad94a625d9afb9c19b","unresolved":true,"context_lines":[{"line_number":31,"context_line":"    INIT \u003d \u0027init\u0027"},{"line_number":32,"context_line":"    WAITING_FOR_VRF \u003d \u0027waiting_for_vrf\u0027"},{"line_number":33,"context_line":"    WAITING_FOR_PORT_BINDING \u003d \u0027waiting_for_port_binding\u0027"},{"line_number":34,"context_line":"    PROVISIONING \u003d \u0027provisioning\u0027"},{"line_number":35,"context_line":"    ADVERTISING \u003d \u0027advertising\u0027"},{"line_number":36,"context_line":"    DEPROVISIONING \u003d \u0027deprovisioning\u0027"},{"line_number":37,"context_line":"    DESTROY \u003d \u0027destroy\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"19059dec_b6bf8bb5","line":34,"in_reply_to":"b96dce33_dc258694","updated":"2026-09-23 11:52:11.000000000","message":"I understand all that. What would not work if those two states did not exist and we transitioned to the next state already. To me the states seem like they reflect where the devices we need are rather than what\u0027s going on with the EVPN instance in OVN.","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"fe7c8848d963eb1e5f5600bc77ffb3abe9e898bb","unresolved":true,"context_lines":[{"line_number":176,"context_line":"            self._svd, self._driver, evpn.vrf, evpn.vni, evpn.vid).execute()"},{"line_number":177,"context_line":"        evpn.vrf_up \u003d False"},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":180,"context_line":"        pass"},{"line_number":181,"context_line":""},{"line_number":182,"context_line":"    def _start_provision_worker(self, evpn):"},{"line_number":183,"context_line":"        cmd \u003d evpn_commands.ProvisionCommand("}],"source_content_type":"text/x-python","patch_set":2,"id":"607fec02_940cb1db","line":180,"range":{"start_line":179,"start_character":0,"end_line":180,"end_character":12},"updated":"2026-09-22 14:39:46.000000000","message":"I commented on the previous PS - I don\u0027t think having a noop is needed.","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"a14bd411df574b6a9417af26ae6cd85ffb218cb7","unresolved":false,"context_lines":[{"line_number":176,"context_line":"            self._svd, self._driver, evpn.vrf, evpn.vni, evpn.vid).execute()"},{"line_number":177,"context_line":"        evpn.vrf_up \u003d False"},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":180,"context_line":"        pass"},{"line_number":181,"context_line":""},{"line_number":182,"context_line":"    def _start_provision_worker(self, evpn):"},{"line_number":183,"context_line":"        cmd \u003d evpn_commands.ProvisionCommand("}],"source_content_type":"text/x-python","patch_set":2,"id":"9ef0cb4b_0ded1d22","line":180,"range":{"start_line":179,"start_character":0,"end_line":180,"end_character":12},"in_reply_to":"607fec02_940cb1db","updated":"2026-09-30 02:11:16.000000000","message":"Done","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"fe7c8848d963eb1e5f5600bc77ffb3abe9e898bb","unresolved":true,"context_lines":[{"line_number":223,"context_line":"    def advance(self, event, vrf, **kwargs):"},{"line_number":224,"context_line":"        \"\"\"Drive FSM state transition for a VRF in response to an event.\"\"\""},{"line_number":225,"context_line":"        with _FSM_LOCK:"},{"line_number":226,"context_line":"            if event in _INTERNAL_EVENTS:"},{"line_number":227,"context_line":"                evpn \u003d self.instances.get(vrf)"},{"line_number":228,"context_line":"                if evpn is None:"},{"line_number":229,"context_line":"                    return"},{"line_number":230,"context_line":"            else:"},{"line_number":231,"context_line":"                evpn \u003d self.instances.setdefault(vrf, EVPNInstance(vrf))"},{"line_number":232,"context_line":"            try:"},{"line_number":233,"context_line":"                new_state, callback_name \u003d self.TRANSITIONS["}],"source_content_type":"text/x-python","patch_set":2,"id":"442f682a_ed06d7a5","line":230,"range":{"start_line":226,"start_character":0,"end_line":230,"end_character":17},"updated":"2026-09-22 14:39:46.000000000","message":"Why do we need this?","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"3c936c6a083a7ae7351ac2e8fdcc9354168f9ef6","unresolved":true,"context_lines":[{"line_number":223,"context_line":"    def advance(self, event, vrf, **kwargs):"},{"line_number":224,"context_line":"        \"\"\"Drive FSM state transition for a VRF in response to an event.\"\"\""},{"line_number":225,"context_line":"        with _FSM_LOCK:"},{"line_number":226,"context_line":"            if event in _INTERNAL_EVENTS:"},{"line_number":227,"context_line":"                evpn \u003d self.instances.get(vrf)"},{"line_number":228,"context_line":"                if evpn is None:"},{"line_number":229,"context_line":"                    return"},{"line_number":230,"context_line":"            else:"},{"line_number":231,"context_line":"                evpn \u003d self.instances.setdefault(vrf, EVPNInstance(vrf))"},{"line_number":232,"context_line":"            try:"},{"line_number":233,"context_line":"                new_state, callback_name \u003d self.TRANSITIONS["}],"source_content_type":"text/x-python","patch_set":2,"id":"56e4cb49_98def39e","line":230,"range":{"start_line":226,"start_character":0,"end_line":230,"end_character":17},"in_reply_to":"442f682a_ed06d7a5","updated":"2026-09-23 02:04:11.000000000","message":"This also stems from the fact that we have retry worker. \nUp until now, \"external events\" were coming PB or Netlink. Now events can also come from the retry worker thread like `PROVISION_DONE` which would be \"internal events\". We can talk more tomorrow :)","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"0841e3c11d13b51b3379ac9c5635b90023d6b069","unresolved":false,"context_lines":[{"line_number":223,"context_line":"    def advance(self, event, vrf, **kwargs):"},{"line_number":224,"context_line":"        \"\"\"Drive FSM state transition for a VRF in response to an event.\"\"\""},{"line_number":225,"context_line":"        with _FSM_LOCK:"},{"line_number":226,"context_line":"            if event in _INTERNAL_EVENTS:"},{"line_number":227,"context_line":"                evpn \u003d self.instances.get(vrf)"},{"line_number":228,"context_line":"                if evpn is None:"},{"line_number":229,"context_line":"                    return"},{"line_number":230,"context_line":"            else:"},{"line_number":231,"context_line":"                evpn \u003d self.instances.setdefault(vrf, EVPNInstance(vrf))"},{"line_number":232,"context_line":"            try:"},{"line_number":233,"context_line":"                new_state, callback_name \u003d self.TRANSITIONS["}],"source_content_type":"text/x-python","patch_set":2,"id":"6ff50179_3fd90814","line":230,"range":{"start_line":226,"start_character":0,"end_line":230,"end_character":17},"in_reply_to":"56e4cb49_98def39e","updated":"2026-09-25 18:25:45.000000000","message":"Done","commit_id":"7ecb1577a19204f3cb5ba787274cba3b3218660f"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"26dd6a557aabef501f154e02d3b0d2e608ddd0b9","unresolved":true,"context_lines":[{"line_number":80,"context_line":"            (EVPNState.ADVERTISING, \"_set_pb_and_advertise\"),"},{"line_number":81,"context_line":"        (EVPNState.WAITING_FOR_PORT_BINDING, EVPNEvent.VRF_DELETE):"},{"line_number":82,"context_line":"            (EVPNState.DESTROY, \"_destroy\"),"},{"line_number":83,"context_line":"        # -- ADVERTISING --"},{"line_number":84,"context_line":"        (EVPNState.ADVERTISING, EVPNEvent.PORT_BINDING_DELETE):"},{"line_number":85,"context_line":"            (EVPNState.WAITING_FOR_PORT_BINDING,"},{"line_number":86,"context_line":"             \"_unadvertise_and_clear_pb\"),"}],"source_content_type":"text/x-python","patch_set":11,"id":"c66e6d46_41559308","line":83,"range":{"start_line":83,"start_character":0,"end_line":83,"end_character":27},"updated":"2026-09-30 12:54:51.000000000","message":"my nit. Delete these comments, not needed","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":false,"context_lines":[{"line_number":80,"context_line":"            (EVPNState.ADVERTISING, \"_set_pb_and_advertise\"),"},{"line_number":81,"context_line":"        (EVPNState.WAITING_FOR_PORT_BINDING, EVPNEvent.VRF_DELETE):"},{"line_number":82,"context_line":"            (EVPNState.DESTROY, \"_destroy\"),"},{"line_number":83,"context_line":"        # -- ADVERTISING --"},{"line_number":84,"context_line":"        (EVPNState.ADVERTISING, EVPNEvent.PORT_BINDING_DELETE):"},{"line_number":85,"context_line":"            (EVPNState.WAITING_FOR_PORT_BINDING,"},{"line_number":86,"context_line":"             \"_unadvertise_and_clear_pb\"),"}],"source_content_type":"text/x-python","patch_set":11,"id":"14300771_34186d1e","line":83,"range":{"start_line":83,"start_character":0,"end_line":83,"end_character":27},"in_reply_to":"c66e6d46_41559308","updated":"2026-10-08 21:12:04.000000000","message":"Done","commit_id":"98f3f1cadb291807106ffed856fbe17879ef9cc6"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"d8b741593913bb2e11fc1e26e69185ad2836abc2","unresolved":true,"context_lines":[{"line_number":109,"context_line":"        evpn.vrf_up \u003d True"},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"    def _set_vrf_and_advertise(self, evpn):"},{"line_number":112,"context_line":"        evpn.vrf_up \u003d True"},{"line_number":113,"context_line":"        self._exec.provision(evpn)"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":"    def _set_pb_and_advertise(self, evpn, mac, vni, vid):"}],"source_content_type":"text/x-python","patch_set":12,"id":"5dafab4a_23158ea1","line":112,"updated":"2026-10-07 20:28:42.000000000","message":"it appears that we actually never use the `vrf_up` anywhere except tests. I know it\u0027s not introduced by this patch but since we\u0027re setting it in some places maybe we should not do it and remove the attribute instead?","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"63a1922ec74d00dceebd9a047c1d7d0713c71dbc","unresolved":false,"context_lines":[{"line_number":109,"context_line":"        evpn.vrf_up \u003d True"},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"    def _set_vrf_and_advertise(self, evpn):"},{"line_number":112,"context_line":"        evpn.vrf_up \u003d True"},{"line_number":113,"context_line":"        self._exec.provision(evpn)"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":"    def _set_pb_and_advertise(self, evpn, mac, vni, vid):"}],"source_content_type":"text/x-python","patch_set":12,"id":"4df98808_1e597380","line":112,"in_reply_to":"5dafab4a_23158ea1","updated":"2026-10-08 21:12:04.000000000","message":"Done","commit_id":"748238843f3e1631dc6a5345e80b7f67b75f0bce"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"d956edf3ecb6308498992fc7d623aa33a26c6be6","unresolved":true,"context_lines":[{"line_number":98,"context_line":"                \"before advancing the FSM\"))"},{"line_number":99,"context_line":"        return self._executor"},{"line_number":100,"context_line":""},{"line_number":101,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":102,"context_line":"        pass"},{"line_number":103,"context_line":""},{"line_number":104,"context_line":"    def _set_port_binding(self, evpn, mac, vni, vid):"},{"line_number":105,"context_line":"        evpn.mac \u003d mac"}],"source_content_type":"text/x-python","patch_set":13,"id":"fa716022_c0f2c8dd","line":102,"range":{"start_line":101,"start_character":0,"end_line":102,"end_character":12},"updated":"2026-10-08 23:14:59.000000000","message":"No-action-need: Only a suggestion: we can do on L140\n\n```\nif not callback_name:\n    LOG.info(\"Transitioning\")\n    return\n```\nand avoid extending the class with the `_noop` method and put either an empty string or None to the TRANSITIONS schema.","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"c7ae070a45b6d0ff6976a739cf4365b215beaf31","unresolved":false,"context_lines":[{"line_number":98,"context_line":"                \"before advancing the FSM\"))"},{"line_number":99,"context_line":"        return self._executor"},{"line_number":100,"context_line":""},{"line_number":101,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":102,"context_line":"        pass"},{"line_number":103,"context_line":""},{"line_number":104,"context_line":"    def _set_port_binding(self, evpn, mac, vni, vid):"},{"line_number":105,"context_line":"        evpn.mac \u003d mac"}],"source_content_type":"text/x-python","patch_set":13,"id":"44cc1165_96f9abc7","line":102,"range":{"start_line":101,"start_character":0,"end_line":102,"end_character":12},"in_reply_to":"fa716022_c0f2c8dd","updated":"2026-10-09 14:49:30.000000000","message":"Acknowledged","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"77556731d6c8a6a10b22280b04540418d8878a23","unresolved":true,"context_lines":[{"line_number":92,"context_line":"        self._executor \u003d evpn_executor.EvpnExecutor("},{"line_number":93,"context_line":"            svd, config.br_mtu, frr_driver)"},{"line_number":94,"context_line":""},{"line_number":95,"context_line":"    @property"},{"line_number":96,"context_line":"    def _exec(self):"},{"line_number":97,"context_line":"        if self._executor is None:"},{"line_number":98,"context_line":"            raise RuntimeError(_("},{"line_number":99,"context_line":"                \"EvpnFSM.setup() must be called \""},{"line_number":100,"context_line":"                \"before advancing the FSM\"))"},{"line_number":101,"context_line":"        return self._executor"},{"line_number":102,"context_line":""},{"line_number":103,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":104,"context_line":"        pass"}],"source_content_type":"text/x-python","patch_set":14,"id":"0b5c89bc_06b74a9b","line":101,"range":{"start_line":95,"start_character":0,"end_line":101,"end_character":29},"updated":"2026-10-12 15:41:37.000000000","message":"Wouldn\u0027t this essentially mean a bug in the code? We should never advance without the executor being instantiated. I think it\u0027s better to have a traceback with AttributeError in case we call to the executor than checking if the executor instance exists with an every single call.","commit_id":"86dd9852a11ce7be810fabed06c3ebc7c8646873"},{"author":{"_account_id":34271,"name":"Miro Tomaska","display_name":"Miro Tomaska","email":"mtomaska@redhat.com","username":"mtomaska"},"change_message_id":"1ab9c263fa200361762f4591683788571795870e","unresolved":false,"context_lines":[{"line_number":92,"context_line":"        self._executor \u003d evpn_executor.EvpnExecutor("},{"line_number":93,"context_line":"            svd, config.br_mtu, frr_driver)"},{"line_number":94,"context_line":""},{"line_number":95,"context_line":"    @property"},{"line_number":96,"context_line":"    def _exec(self):"},{"line_number":97,"context_line":"        if self._executor is None:"},{"line_number":98,"context_line":"            raise RuntimeError(_("},{"line_number":99,"context_line":"                \"EvpnFSM.setup() must be called \""},{"line_number":100,"context_line":"                \"before advancing the FSM\"))"},{"line_number":101,"context_line":"        return self._executor"},{"line_number":102,"context_line":""},{"line_number":103,"context_line":"    def _noop(self, evpn, **kwargs):"},{"line_number":104,"context_line":"        pass"}],"source_content_type":"text/x-python","patch_set":14,"id":"2d503ed2_7364b088","line":101,"range":{"start_line":95,"start_character":0,"end_line":101,"end_character":29},"in_reply_to":"0b5c89bc_06b74a9b","updated":"2026-10-12 17:39:01.000000000","message":"Done","commit_id":"86dd9852a11ce7be810fabed06c3ebc7c8646873"},{"author":{"_account_id":8655,"name":"Jakub Libosvar","email":"libosvar@redhat.com","username":"jlibosva"},"change_message_id":"0d8db91f10aa7eac33cda1d151cec6f81e3e7dfc","unresolved":true,"context_lines":[{"line_number":56,"context_line":"        return self.vrf \u003d\u003d other.vrf"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def __hash__(self):"},{"line_number":59,"context_line":"        return hash(self.vrf)"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":""},{"line_number":62,"context_line":"class EvpnFSM:"}],"source_content_type":"text/x-python","patch_set":17,"id":"f21aed44_a70a18c7","line":59,"updated":"2026-10-18 17:09:54.000000000","message":"The point of making this hash was to have the same key for FSM instances and the executor instances - https://review.opendev.org/c/openstack/neutron/+/1006431/17/neutron/agent/ovn/extensions/evpn/fsm.py#134\n\nI think if we hash the vrf, the keys would differ.","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"786a50202de2e8e44584b1136331ea9d223b46c9","unresolved":true,"context_lines":[{"line_number":56,"context_line":"        return self.vrf \u003d\u003d other.vrf"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def __hash__(self):"},{"line_number":59,"context_line":"        return hash(self.vrf)"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":""},{"line_number":62,"context_line":"class EvpnFSM:"}],"source_content_type":"text/x-python","patch_set":17,"id":"f9ea3b67_70a5fdaa","line":59,"in_reply_to":"f21aed44_a70a18c7","updated":"2026-10-19 13:37:02.000000000","message":"I don\u0027t think the point was to have the key match so much as just to have the key stable in the executor if a new instance came in with a matching vrf and different vni, i.e. to basically do the same thing as we do in the referenced code by keying off of the VRF, but to do it in a way w/o changing all of the existing executor code.\n\nI personally think that vrf key/setdefault is a more clear expression of the \"don\u0027t update this key if the vrf already exists\" than the hash change. There is a little confusion with the executor code where \"only update the EvpnInstance key if it doesn\u0027t already exist, but the values that we pass to Task are the values from the new key.\"\n\nI +2\u0027d because there isn\u0027t a bug caused by the __hash__ implementation, but I do prefer the other (or at least some commenting about they key/value difference, etc.).","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"0fb8ec4121f5c45c1abb2c9decae2ff49cbbb3c8","unresolved":false,"context_lines":[{"line_number":56,"context_line":"        return self.vrf \u003d\u003d other.vrf"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def __hash__(self):"},{"line_number":59,"context_line":"        return hash(self.vrf)"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":""},{"line_number":62,"context_line":"class EvpnFSM:"}],"source_content_type":"text/x-python","patch_set":17,"id":"629ff1e2_063cf650","line":59,"in_reply_to":"f9ea3b67_70a5fdaa","updated":"2026-10-20 13:48:39.000000000","message":"Done","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"628e1d9e5ae653c0727a8bc40aeb992d33913ab1","unresolved":false,"context_lines":[{"line_number":50,"context_line":"        self.vid \u003d None"},{"line_number":51,"context_line":"        self.state \u003d EVPNState.INIT"},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"    def __eq__(self, other):"},{"line_number":54,"context_line":"        if not isinstance(other, EVPNInstance):"},{"line_number":55,"context_line":"            return NotImplemented"},{"line_number":56,"context_line":"        return self.vrf \u003d\u003d other.vrf"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def __hash__(self):"},{"line_number":59,"context_line":"        return hash(self.vrf)"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":""},{"line_number":62,"context_line":"class EvpnFSM:"},{"line_number":63,"context_line":"    \"\"\"Finite State Machine for EVPN instances."}],"source_content_type":"text/x-python","patch_set":17,"id":"7b68b945_2e97e8c2","line":60,"range":{"start_line":53,"start_character":0,"end_line":60,"end_character":0},"updated":"2026-10-16 16:31:50.000000000","message":"We do have to be careful with this. It currently works in the executor because we pass the evpn attributes themselves to the _Task along with the EvpnInstance as a key and *only* use the EvpnInstance as a key. In a case case like:\n```\n  e1 \u003d EvpnInstance(vrf\u003d\"a\", vni\u003d1)\n  e2 \u003d EvpnInstance(vrf\u003d\"ab\", vni\u003d2)\n  d \u003d {}\n  d[e1] \u003d \"task1\"\n  d[e2] \u003d \"task2\"\n  for x, y in d.items():\n      print(x.vni, y)\n```\nwould show the original e1 as the key with vni 1, with the \"task2\" as the value, which could be surprising. If someone ever came by and looked at the code and was like \"we are passing the evpn and the args, I can simplify that\" it would break. I think it\u0027s functionally similar to the way we use keying off of vrf and `setdefault()` in `advance()` And obviously nobody should write code expecting two EvpnInstances equality to include anything other than VRF in the future. :)","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"},{"author":{"_account_id":5756,"name":"Terry Wilson","email":"twilson@redhat.com","username":"otherwiseguy"},"change_message_id":"7939199c44d5530f5143dc1af8cbcfde90dd68cf","unresolved":false,"context_lines":[{"line_number":50,"context_line":"        self.vid \u003d None"},{"line_number":51,"context_line":"        self.state \u003d EVPNState.INIT"},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"    def __eq__(self, other):"},{"line_number":54,"context_line":"        if not isinstance(other, EVPNInstance):"},{"line_number":55,"context_line":"            return NotImplemented"},{"line_number":56,"context_line":"        return self.vrf \u003d\u003d other.vrf"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def __hash__(self):"},{"line_number":59,"context_line":"        return hash(self.vrf)"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":""},{"line_number":62,"context_line":"class EvpnFSM:"},{"line_number":63,"context_line":"    \"\"\"Finite State Machine for EVPN instances."}],"source_content_type":"text/x-python","patch_set":17,"id":"9d09f159_767264a8","line":60,"range":{"start_line":53,"start_character":0,"end_line":60,"end_character":0},"in_reply_to":"7b68b945_2e97e8c2","updated":"2026-10-16 17:01:08.000000000","message":"s/\"ab\"/\"a\". typo","commit_id":"7e719e54c503e50c6d75253cfacb65b592779ab0"}],"neutron/tests/unit/agent/ovn/extensions/evpn/test_executor.py":[{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9a78cd26dc5494acfec9ed96661d60ccac7f38b6","unresolved":false,"context_lines":[{"line_number":300,"context_line":"            self.assertEqual(0, len(self.executor._deferred))"},{"line_number":301,"context_line":""},{"line_number":302,"context_line":"    def test_inflight_provision_then_newer_provision_runs(self):"},{"line_number":303,"context_line":"        \"\"\"Newer provision supersedes in-flight;"},{"line_number":304,"context_line":""},{"line_number":305,"context_line":"           final _do_provision uses new args.\"\"\""},{"line_number":306,"context_line":"        evpn \u003d self._make_evpn(vni\u003d10, vid\u003d1)"}],"source_content_type":"text/x-python","patch_set":13,"id":"9b067386_a4290235","line":303,"updated":"2026-10-08 23:00:14.000000000","message":"pep8: H403: multi line docstrings should end on a new line","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9a78cd26dc5494acfec9ed96661d60ccac7f38b6","unresolved":false,"context_lines":[{"line_number":301,"context_line":""},{"line_number":302,"context_line":"    def test_inflight_provision_then_newer_provision_runs(self):"},{"line_number":303,"context_line":"        \"\"\"Newer provision supersedes in-flight;"},{"line_number":304,"context_line":""},{"line_number":305,"context_line":"           final _do_provision uses new args.\"\"\""},{"line_number":306,"context_line":"        evpn \u003d self._make_evpn(vni\u003d10, vid\u003d1)"},{"line_number":307,"context_line":"        worker_blocked \u003d threading.Event()"}],"source_content_type":"text/x-python","patch_set":13,"id":"8d3b68b6_df438edb","line":304,"updated":"2026-10-08 23:00:14.000000000","message":"pep8: H403: multi line docstrings should end on a new line","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9a78cd26dc5494acfec9ed96661d60ccac7f38b6","unresolved":false,"context_lines":[{"line_number":302,"context_line":"    def test_inflight_provision_then_newer_provision_runs(self):"},{"line_number":303,"context_line":"        \"\"\"Newer provision supersedes in-flight;"},{"line_number":304,"context_line":""},{"line_number":305,"context_line":"           final _do_provision uses new args.\"\"\""},{"line_number":306,"context_line":"        evpn \u003d self._make_evpn(vni\u003d10, vid\u003d1)"},{"line_number":307,"context_line":"        worker_blocked \u003d threading.Event()"},{"line_number":308,"context_line":"        gate \u003d threading.Event()"}],"source_content_type":"text/x-python","patch_set":13,"id":"5e455e64_7d891975","line":305,"updated":"2026-10-08 23:00:14.000000000","message":"pep8: H403: multi line docstrings should end on a new line","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9a78cd26dc5494acfec9ed96661d60ccac7f38b6","unresolved":false,"context_lines":[{"line_number":329,"context_line":"            mock_do_prov.assert_called_with(\u0027vrf1\u0027, 20, 2, MAC)"},{"line_number":330,"context_line":""},{"line_number":331,"context_line":"    def test_inflight_success_then_queued_deprovision(self):"},{"line_number":332,"context_line":"        \"\"\"In-flight provision cannot be aborted; queued deprovision runs after"},{"line_number":333,"context_line":"        .\"\"\""},{"line_number":334,"context_line":"        evpn \u003d self._make_evpn()"},{"line_number":335,"context_line":"        worker_blocked \u003d threading.Event()"}],"source_content_type":"text/x-python","patch_set":13,"id":"67ca95c1_1587471a","line":332,"updated":"2026-10-08 23:00:14.000000000","message":"pep8: H403: multi line docstrings should end on a new line","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9a78cd26dc5494acfec9ed96661d60ccac7f38b6","unresolved":false,"context_lines":[{"line_number":330,"context_line":""},{"line_number":331,"context_line":"    def test_inflight_success_then_queued_deprovision(self):"},{"line_number":332,"context_line":"        \"\"\"In-flight provision cannot be aborted; queued deprovision runs after"},{"line_number":333,"context_line":"        .\"\"\""},{"line_number":334,"context_line":"        evpn \u003d self._make_evpn()"},{"line_number":335,"context_line":"        worker_blocked \u003d threading.Event()"},{"line_number":336,"context_line":"        gate \u003d threading.Event()"}],"source_content_type":"text/x-python","patch_set":13,"id":"577d41cc_3db2a45c","line":333,"updated":"2026-10-08 23:00:14.000000000","message":"pep8: H403: multi line docstrings should end on a new line","commit_id":"90d71b4c5b9531b2f9d3de643f21251aa3bc9057"}],"neutron/tests/unit/agent/ovn/extensions/evpn/test_fsm.py":[{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":53,"context_line":"                             \"Executor thread did not finish in time\")"},{"line_number":54,"context_line":"        self._executor_threads.clear()"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":"    # -- happy-path tests -------------------------------------------------------"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    def test_vrf_then_port_binding_create(self):"},{"line_number":59,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"28e1b8fe_32d002ab","line":56,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":131,"context_line":"        self.mock_svd.del_vni.assert_called_once_with(10, 1)"},{"line_number":132,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":133,"context_line":""},{"line_number":134,"context_line":"    # -- concurrent tests -------------------------------------------------------"},{"line_number":135,"context_line":""},{"line_number":136,"context_line":"    def test_simultaneous_vrf_and_port_binding_create(self):"},{"line_number":137,"context_line":"        \"\"\"Netlink and SB IDL threads both create for the same VRF.\"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"690bf866_f72dbde9","line":134,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":197,"context_line":"        self._join_executor()"},{"line_number":198,"context_line":"        self.assertNotIn(vrf, self.evpn_fsm.instances)"},{"line_number":199,"context_line":""},{"line_number":200,"context_line":"    # -- bounce / recovery tests ------------------------------------------------"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"    def test_advertise_then_vrf_delete_then_vrf_create(self):"},{"line_number":203,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"3b1165af_8ca7ad61","line":200,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":257,"context_line":"        self.mock_driver.create_router.assert_called_with(vrf, 20)"},{"line_number":258,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":259,"context_line":""},{"line_number":260,"context_line":"    # -- replay tests -----------------------------------------------------------"},{"line_number":261,"context_line":""},{"line_number":262,"context_line":"    def test_replay_end_deletes_stale_fsm_instance(self):"},{"line_number":263,"context_line":"        vrf1, vrf2 \u003d \u0027vr0a1b2c3d-eee\u0027, \u0027vr1a2b3c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"86892688_43fcbd08","line":260,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":303,"context_line":"        self.assertIn(vrf, self.evpn_fsm.instances)"},{"line_number":304,"context_line":"        self.assertEqual({vrf}, handler._known_vrfs)"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"    # -- driver not called before ADVERTISING -----------------------------------"},{"line_number":307,"context_line":""},{"line_number":308,"context_line":"    def test_driver_not_called_before_advertising(self):"},{"line_number":309,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"212cd26e_047876ed","line":306,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":324,"context_line":"        self.mock_driver.create_router.assert_not_called()"},{"line_number":325,"context_line":"        self.mock_driver.delete_router.assert_not_called()"},{"line_number":326,"context_line":""},{"line_number":327,"context_line":"    # -- executor retry / failure tests -----------------------------------------"},{"line_number":328,"context_line":""},{"line_number":329,"context_line":"    def test_executor_retries_on_provision_failure(self):"},{"line_number":330,"context_line":"        \"\"\"Executor retries on provision failure then succeeds.\"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"a7f2921d_9d9f1901","line":327,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":364,"context_line":"        self.assertEqual(fsm.EVPNState.ADVERTISING, evpn.state)"},{"line_number":365,"context_line":"        self.assertEqual(2, self.mock_svd.add_vni.call_count)"},{"line_number":366,"context_line":""},{"line_number":367,"context_line":"    # -- cancel-on-replace tests ------------------------------------------------"},{"line_number":368,"context_line":""},{"line_number":369,"context_line":"    def test_pb_delete_cancels_provision_and_deprovisions(self):"},{"line_number":370,"context_line":"        \"\"\"PB_DELETE while provision is running -\u003e cancel + deprovision.\"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"c8653725_8551829d","line":367,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"d8728e7693e3dbe0e0cfd541ceb8199b734b3d1e","unresolved":false,"context_lines":[{"line_number":545,"context_line":"        self.assertEqual(2, self.mock_svd.del_vni.call_count)"},{"line_number":546,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":547,"context_line":""},{"line_number":548,"context_line":"    # -- full cycle test --------------------------------------------------------"},{"line_number":549,"context_line":""},{"line_number":550,"context_line":"    def test_cancel_recovery_full_cycle(self):"},{"line_number":551,"context_line":"        \"\"\"ADVERTISING -\u003e PB_DELETE -\u003e WAITING -\u003e PB_CREATE -\u003e ADVERTISING.\"\"\""}],"source_content_type":"text/x-python","patch_set":4,"id":"a92be49f_c01567e5","line":548,"updated":"2026-09-25 03:11:01.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"822370b92e37e1a4fa6915211c6bc4493c39bb00"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":55,"context_line":"                             \"Executor thread did not finish in time\")"},{"line_number":56,"context_line":"        self._executor_threads.clear()"},{"line_number":57,"context_line":""},{"line_number":58,"context_line":"    # -- happy-path tests -------------------------------------------------------"},{"line_number":59,"context_line":""},{"line_number":60,"context_line":"    def test_vrf_then_port_binding_create(self):"},{"line_number":61,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":5,"id":"cef9761f_0f38f9b4","line":58,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":133,"context_line":"        self.mock_svd.del_vni.assert_called_once_with(10, 1)"},{"line_number":134,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":135,"context_line":""},{"line_number":136,"context_line":"    # -- concurrent tests -------------------------------------------------------"},{"line_number":137,"context_line":""},{"line_number":138,"context_line":"    def test_simultaneous_vrf_and_port_binding_create(self):"},{"line_number":139,"context_line":"        \"\"\"Netlink and SB IDL threads both create for the same VRF.\"\"\""}],"source_content_type":"text/x-python","patch_set":5,"id":"6bf5f486_1e263ed2","line":136,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":199,"context_line":"        self._join_executor()"},{"line_number":200,"context_line":"        self.assertNotIn(vrf, self.evpn_fsm.instances)"},{"line_number":201,"context_line":""},{"line_number":202,"context_line":"    # -- bounce / recovery tests ------------------------------------------------"},{"line_number":203,"context_line":""},{"line_number":204,"context_line":"    def test_advertise_then_vrf_delete_then_vrf_create(self):"},{"line_number":205,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":5,"id":"3d5e1522_1f485ad6","line":202,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":259,"context_line":"        self.mock_driver.create_router.assert_called_with(vrf, 20)"},{"line_number":260,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":261,"context_line":""},{"line_number":262,"context_line":"    # -- replay tests -----------------------------------------------------------"},{"line_number":263,"context_line":""},{"line_number":264,"context_line":"    def test_replay_end_deletes_stale_fsm_instance(self):"},{"line_number":265,"context_line":"        vrf1, vrf2 \u003d \u0027vr0a1b2c3d-eee\u0027, \u0027vr1a2b3c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":5,"id":"ba6af175_91d607cd","line":262,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":305,"context_line":"        self.assertIn(vrf, self.evpn_fsm.instances)"},{"line_number":306,"context_line":"        self.assertEqual({vrf}, handler._known_vrfs)"},{"line_number":307,"context_line":""},{"line_number":308,"context_line":"    # -- driver not called before ADVERTISING -----------------------------------"},{"line_number":309,"context_line":""},{"line_number":310,"context_line":"    def test_driver_not_called_before_advertising(self):"},{"line_number":311,"context_line":"        vrf \u003d \u0027vr0a1b2c3d-fff\u0027"}],"source_content_type":"text/x-python","patch_set":5,"id":"809c5a73_7aa752f6","line":308,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":326,"context_line":"        self.mock_driver.create_router.assert_not_called()"},{"line_number":327,"context_line":"        self.mock_driver.delete_router.assert_not_called()"},{"line_number":328,"context_line":""},{"line_number":329,"context_line":"    # -- executor retry / failure tests -----------------------------------------"},{"line_number":330,"context_line":""},{"line_number":331,"context_line":"    def test_executor_retries_on_provision_failure(self):"},{"line_number":332,"context_line":"        \"\"\"Executor retries on provision failure then succeeds.\"\"\""}],"source_content_type":"text/x-python","patch_set":5,"id":"7659bbc2_488c2e14","line":329,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":366,"context_line":"        self.assertEqual(fsm.EVPNState.ADVERTISING, evpn.state)"},{"line_number":367,"context_line":"        self.assertEqual(2, self.mock_svd.add_vni.call_count)"},{"line_number":368,"context_line":""},{"line_number":369,"context_line":"    # -- cancel-on-replace tests ------------------------------------------------"},{"line_number":370,"context_line":""},{"line_number":371,"context_line":"    def test_pb_delete_cancels_provision_and_deprovisions(self):"},{"line_number":372,"context_line":"        \"\"\"PB_DELETE while provision is running -\u003e cancel + deprovision.\"\"\""}],"source_content_type":"text/x-python","patch_set":5,"id":"dc1cdab8_a7692851","line":369,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"10f6f25db77050ab181c37dfca1b19036a4abadb","unresolved":false,"context_lines":[{"line_number":547,"context_line":"        self.assertEqual(2, self.mock_svd.del_vni.call_count)"},{"line_number":548,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":549,"context_line":""},{"line_number":550,"context_line":"    # -- full cycle test --------------------------------------------------------"},{"line_number":551,"context_line":""},{"line_number":552,"context_line":"    def test_cancel_recovery_full_cycle(self):"},{"line_number":553,"context_line":"        \"\"\"ADVERTISING -\u003e PB_DELETE -\u003e WAITING -\u003e PB_CREATE -\u003e ADVERTISING.\"\"\""}],"source_content_type":"text/x-python","patch_set":5,"id":"765a6964_a49dc081","line":550,"updated":"2026-09-25 19:44:29.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"b20b62306afed89f1be72c781479e3e99be3d8fd"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"9b8a25e3536d028fce114b52334409e8ca1ff77d","unresolved":false,"context_lines":[{"line_number":533,"context_line":"        self.assertEqual(2, self.mock_svd.del_vni.call_count)"},{"line_number":534,"context_line":"        self.mock_driver.delete_router.assert_called_once_with(vrf, 10)"},{"line_number":535,"context_line":""},{"line_number":536,"context_line":"    # -- full cycle test --------------------------------------------------------"},{"line_number":537,"context_line":""},{"line_number":538,"context_line":"    def test_cancel_recovery_full_cycle(self):"},{"line_number":539,"context_line":"        \"\"\"ADVERTISING -\u003e PB_DELETE -\u003e WAITING -\u003e PB_CREATE -\u003e ADVERTISING.\"\"\""}],"source_content_type":"text/x-python","patch_set":8,"id":"482640a6_f288c0c8","line":536,"updated":"2026-09-28 18:07:56.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"fcc0a8acd04c8bdb2aa89c097963536dbd5ff290"}]}
