)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"f6ffbc2db25f7d7c82ebc02c0718af3a1418fca8","unresolved":false,"context_lines":[{"line_number":1,"context_line":"Parent:     ec06744b (Merge \"Add Nova/Placement emulators and host_maintenance functional tests\")"},{"line_number":2,"context_line":"Author:     Douglas Viroel \u003cviroel@gmail.com\u003e"},{"line_number":3,"context_line":"AuthorDate: 2026-05-12 16:32:35 -0300"},{"line_number":4,"context_line":"Commit:     Douglas Viroel \u003cviroel@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":5,"id":"3aaf3bae_a0ebb128","line":1,"updated":"2026-08-26 19:42:41.000000000","message":"The commit message states the strategy \u0027evaluates only the N lightest nodes\u0027 and that get_least_loaded_hosts() \u0027returns the N hosts with the lowest weighted load\u0027. The implementation instead builds a candidate pool from the lightest least_loaded_percent% of hosts (default 50) and randomly samples least_loaded_count hosts from that pool; get_least_loaded_hosts() actually returns all hosts sorted ascending. The new least_loaded_percent parameter and its operator-visible default effect (destinations restricted to the lightest half of nodes) are not mentioned anywhere in the message.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: Operators reading the permanent change history get an inaccurate model of the new mode and are never told about least_loaded_percent or that its default restricts destination candidates to the lightest 50% of nodes.\n\n**Suggestion**:\nUpdate the commit message to describe the two-step selection (percent-based pool, then random sample of least_loaded_count) and add a bullet documenting the least_loaded_percent parameter and its default of 50.","commit_id":"cebcf256655bdc5b1bfa3c7023abded20c4bb543"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3f867bb2e608705613e26e9665fe6fd878c6c6e6","unresolved":false,"context_lines":[{"line_number":1,"context_line":"Parent:     d92323f8 (Merge \"Rename multinode base job to three-node and use three-node nodeset\")"},{"line_number":2,"context_line":"Author:     Douglas Viroel \u003cviroel@gmail.com\u003e"},{"line_number":3,"context_line":"AuthorDate: 2026-05-12 16:32:35 -0300"},{"line_number":4,"context_line":"Commit:     Douglas Viroel \u003cviroel@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":6,"id":"208b82b4_6e3dd6d8","line":1,"updated":"2026-09-15 18:26:00.000000000","message":"The change message states the strategy evaluates destinations \u0027rather than cycling, sampling randomly, or scanning all hosts\u0027, but the implementation randomly samples destinations per source node via random.sample(pool, sample_size) (workload_stabilization.py:539). It says \u0027get_least_loaded_hosts(): returns the N hosts with the lowest weighted load, sorted ascending\u0027, but the function returns ALL hosts sorted ascending (docstring at lines 371-374); the N-trimming happens in simulate_migrations via least_loaded_percent. The message documents only least_loaded_count and never mentions the second new operator-facing parameter, least_loaded_percent (lines 217-228), which actually defines the candidate pool.\n\n**Severity**: WARNING | **Confidence**: 0.95\n\n**Impact**: Reviewers, release-note authors, and maintainers reading history will believe destination selection is deterministic ranking of the N lightest hosts and that only one parameter exists. Operators tuning the feature will not learn that least_loaded_percent exists or that selection is randomized, which is needed to understand and operate the change.\n\n**Suggestion**:\nAmend the commit message (e.g. via a follow-up rebase before merge) to state that least_loaded randomly samples least_loaded_count destinations from the lightest least_loaded_percent% of hosts, that selection is randomized per source node, and that get_least_loaded_hosts returns all hosts sorted by weighted load while the percent-based trimming happens in simulate_migrations.","commit_id":"12ce1ec84df4a22a5a39e0263825259f6576ccd1"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"65be6194d6540553cd4001f22fab2798b74346c7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"a992414d_1cd6e1bc","updated":"2026-05-12 20:01:01.000000000","message":"WIP - still getting more results from simulations","commit_id":"9dee4bd877122a32588a1dc380061dbe802e1820"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"fb589dc66fcbcf6fe2bc87a6c9d31b34e8b1ef71","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"6b0897b4_f892b040","updated":"2026-05-29 12:49:33.000000000","message":"Needs rebase on other fixes","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"232e6419_161d651c","updated":"2026-06-09 16:52:12.000000000","message":"This change should help to find better migration candidates in the simulate_migrations by pre-selecting destination candidates in a more smart way that pure random. This should have the effect of getting better actionplans and reducing SDs with less migrations.\n\nSaid that, we still carry the pre-existing overall implementation which is suboptimal (see my inline comments).\n\nMy tests of this (together with https://review.opendev.org/c/openstack/watcher/+/988533 and the locking improvements) shown that results are good and execution time reduces almost linearly by decreasing source_nodes_percent, what may be good enough for our requirements so far.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"}],"doc/source/strategies/workload-stabilization.rst":[{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"67eafa2137ba6adc92a726f32e7d63da98aded8b","unresolved":true,"context_lines":[{"line_number":68,"context_line":""},{"line_number":69,"context_line":"Strategy parameters are:"},{"line_number":70,"context_line":""},{"line_number":71,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":72,"context_line":"parameter              type   default Value       description"},{"line_number":73,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":74,"context_line":"``metrics``            array  |metrics|           Metrics used as rates of"}],"source_content_type":"text/x-rst","patch_set":7,"id":"5d0425ee_bbe35eb0","side":"PARENT","line":71,"range":{"start_line":71,"start_character":0,"end_line":71,"end_character":79},"updated":"2026-09-16 10:59:08.000000000","message":"Note: it was a pita to maintain this table like this, while adding new lines so I refactored.","commit_id":"d92323f80f7e6c38755bcdeba5e8eb9526fecd58"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"900c5411d0ed24680a68cb04920bad749bf10d22","unresolved":true,"context_lines":[{"line_number":68,"context_line":""},{"line_number":69,"context_line":"Strategy parameters are:"},{"line_number":70,"context_line":""},{"line_number":71,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":72,"context_line":"parameter              type   default Value       description"},{"line_number":73,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":74,"context_line":"``metrics``            array  |metrics|           Metrics used as rates of"}],"source_content_type":"text/x-rst","patch_set":7,"id":"a50fbd5a_f6506c64","side":"PARENT","line":71,"range":{"start_line":71,"start_character":0,"end_line":71,"end_character":79},"in_reply_to":"431965c9_0b529e98","updated":"2026-09-23 11:06:47.000000000","message":"+1 thanks","commit_id":"d92323f80f7e6c38755bcdeba5e8eb9526fecd58"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":68,"context_line":""},{"line_number":69,"context_line":"Strategy parameters are:"},{"line_number":70,"context_line":""},{"line_number":71,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":72,"context_line":"parameter              type   default Value       description"},{"line_number":73,"context_line":"\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d"},{"line_number":74,"context_line":"``metrics``            array  |metrics|           Metrics used as rates of"}],"source_content_type":"text/x-rst","patch_set":7,"id":"431965c9_0b529e98","side":"PARENT","line":71,"range":{"start_line":71,"start_character":0,"end_line":71,"end_character":79},"in_reply_to":"5d0425ee_bbe35eb0","updated":"2026-09-17 18:07:05.000000000","message":"yep its a lot more maintaine using the proper tabels\n\nles readable in source form but the trade off is worth it","commit_id":"d92323f80f7e6c38755bcdeba5e8eb9526fecd58"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"67eafa2137ba6adc92a726f32e7d63da98aded8b","unresolved":true,"context_lines":[{"line_number":114,"context_line":"       instances. There are cycle, retry, fullsearch and least_loaded"},{"line_number":115,"context_line":"       methods. Cycle will iterate hosts in cycle. Retry will get some"},{"line_number":116,"context_line":"       hosts random (count defined in retry_count option). Fullsearch"},{"line_number":117,"context_line":"       will return each host from list. Least_loaded will only consider"},{"line_number":118,"context_line":"       the lightest hosts, see the least_loaded_percent and"},{"line_number":119,"context_line":"       least_loaded_count options."},{"line_number":120,"context_line":"   * - ``retry_count``"},{"line_number":121,"context_line":"     - number"},{"line_number":122,"context_line":"     - 1"}],"source_content_type":"text/x-rst","patch_set":7,"id":"2aaeac2c_489e64b1","line":119,"range":{"start_line":117,"start_character":40,"end_line":119,"end_character":34},"updated":"2026-09-16 10:59:08.000000000","message":"This is new.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"67eafa2137ba6adc92a726f32e7d63da98aded8b","unresolved":true,"context_lines":[{"line_number":121,"context_line":"     - number"},{"line_number":122,"context_line":"     - 1"},{"line_number":123,"context_line":"     - Count of random returned hosts."},{"line_number":124,"context_line":"   * - ``least_loaded_percent``"},{"line_number":125,"context_line":"     - number"},{"line_number":126,"context_line":"     - 50"},{"line_number":127,"context_line":"     - Percentage of the lightest hosts, by weighted load, that form"},{"line_number":128,"context_line":"       the destination candidate pool when host_choice is least_loaded."},{"line_number":129,"context_line":"       Hosts are ranked by a normalized load computed across all the"},{"line_number":130,"context_line":"       configured metrics, using the values of the weights parameter."},{"line_number":131,"context_line":"       The default value of 50 means that only the lightest half of the"},{"line_number":132,"context_line":"       available nodes is eligible as destination."},{"line_number":133,"context_line":"   * - ``least_loaded_count``"},{"line_number":134,"context_line":"     - number"},{"line_number":135,"context_line":"     - 1"},{"line_number":136,"context_line":"     - Number of destination hosts randomly sampled from the pool"},{"line_number":137,"context_line":"       defined by least_loaded_percent, and evaluated for each source"},{"line_number":138,"context_line":"       node, when host_choice is least_loaded."},{"line_number":139,"context_line":"   * - ``periods``"},{"line_number":140,"context_line":"     - object"},{"line_number":141,"context_line":"     - |periods|"}],"source_content_type":"text/x-rst","patch_set":7,"id":"5be2066d_ddcdb15d","line":138,"range":{"start_line":124,"start_character":0,"end_line":138,"end_character":46},"updated":"2026-09-16 10:59:08.000000000","message":"New","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"}],"releasenotes/notes/workload-stabilization-least-loaded-host-choice-0187f8b518399432.yaml":[{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"6f508d38_1c7de06d","line":30,"updated":"2026-09-17 18:07:05.000000000","message":"ack i was going ot say we dont need to call out \"fixign input validation\" as an upgrade note but hte change in invalid bahvior is note worthy","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"15ab3a16999d471de0b2c5e3fe0accbcc7dc8944","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"d90d0b60_47a8e5c1","line":30,"in_reply_to":"41499656_6f3eab91","updated":"2026-09-23 11:16:51.000000000","message":"ya you have mentioned that before\nform my presepctive that is a pretty severy bug and we shoudl fix that in the futre\ni dont think it really apcpable for upgrade to invalidate any exsting audit\n\ni.e. upgradnign shoudl not result in a continues audit being made invalid even if a stragy gains new options.\n\nso whiel we have nto priitzed that today i think we shoudl be fixing that next cycle\n\nif i had been aware of this behvior before we GA\u0027d supprot for this i would have consier this a ga blocking bug downstream","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"900c5411d0ed24680a68cb04920bad749bf10d22","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"41499656_6f3eab91","line":30,"in_reply_to":"6f508d38_1c7de06d","updated":"2026-09-23 11:06:47.000000000","message":"On upgrades, pre-existing audits with this strategies will be cancelled because of the sync behavior implemented in the decision-engine. At startup, decision-engine removes and re-creates the strategy in db if the parameters spec has changed. Any stratey with the old version of the strategy is cancelled. Example in a upgrade test executed locally\n\n```\n2026-09-23 09:47:38.765 1 INFO watcher.decision_engine.sync [None req-eeb04e14-e542-4088-9de5-409747de56de - - - - - -] Strategy workload_stabilization modified\n2026-09-23 09:47:38.777 1 INFO watcher.decision_engine.sync [None req-eeb04e14-e542-4088-9de5-409747de56de - - - - - -] Strategy workload_stabilization created\n...\n2026-09-23 09:47:38.815 1 INFO watcher.decision_engine.sync [None req-eeb04e14-e542-4088-9de5-409747de56de - - - - - -] Stale audit \u002710333995-933f-4259-b796-2159fd971fc6\u0027 synced and cancelled\n2026-09-23 09:47:38.820 1 INFO watcher.decision_engine.sync [None req-b3800010-7f91-422a-9b57-e66fbaf87093 - - - - - -] Stale action plan \u002782f38d76-8cee-4b97-9a95-748d73c9a9cf\u0027 synced and cancelled\n2026-09-23 09:47:38.824 1 INFO watcher.decision_engine.sync [None req-159c5b77-dee8-4b7a-9868-5285ca27208e - - - - - -] Stale action plan \u002728e10f68-7d0c-4c12-bb40-41a53841ef6a\u0027 synced and cancelled\n```\n\nWhere \u002710333995-933f-4259-b796-2159fd971fc6\u0027 was an ongoing continuous audit with workload_stabilization:\n\n```\n| 10333995-933f-4259-b796-2159fd971fc6 | test1-continuos | CONTINUOUS | CANCELLED | workload_balancing | workload_stabilization | False        |\n```\n\nThis behavior is not caused but this change and it happen on any change on parameters for strateges but I wonder if we should document it in release note.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"f4cb94ee92acb93402f65aae05e9daabeda8c523","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"d41c3cfb_92cc4632","line":30,"in_reply_to":"b4674ffc_953825a9","updated":"2026-09-23 14:56:41.000000000","message":"to be clear, managing updates is a different topic that this review, and it\u0027s not blocking. My comment was just about documentation for the behavior in the context of this change but it\u0027s a different topic out of the scope of this patch.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"01a3974560a0da757e998c22177ecf558fa13092","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"b4674ffc_953825a9","line":30,"in_reply_to":"c7727037_66d5a402","updated":"2026-09-23 13:48:58.000000000","message":"yep its a good topic, we need to have some level of grenade coverage for this and eventually ithink we need to adopt stable uuids for all stragies and plugins\n\nperhasp a uuid5 of the stagey/goal cannoical name with a way to migrate all exsitign isntances.\n\nthe stragey and goal uuids shoudl be the saem across openstack clouds and version ideally and as i noted existing auit shoudl not break via the addtion of optioanal parmter shoudl nto cause the uuid to chagne\n\nthis is explcity covered by the grenede thoery of upgrades which all servcie are expected to conform too\n\nhttps://opendev.org/openstack/grenade/src/branch/master/README.rst?display\u003dsource#L47\n\n```\n- Resources created by services before upgrade, should still be there\n  after the system is upgraded\n```\n\nthe recreation fo the stagey violates that.\n\nand any violation require and explcit release note callout and shoudl be avoided when at all possible.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"fbf6d1267bdc37e8dc9cefc4710970f34a33de2b","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"87fb4dcc_16c154e0","line":30,"in_reply_to":"d41c3cfb_92cc4632","updated":"2026-09-28 16:52:58.000000000","message":"I understand your concern and it is a valid call out. It think that this issue should be highlighted in other place other than this specific release note. We could link here an external documentation to that bug/limitation instead.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"ac35bb8c600ecee5f39f257aec5ef28c94223fec","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    (``cycle``, ``retry``, ``fullsearch`` and ``least_loaded``). Audits that"},{"line_number":28,"context_line":"    used to pass an unrecognized value for this parameter are now rejected at"},{"line_number":29,"context_line":"    audit creation time instead of silently falling back to no host"},{"line_number":30,"context_line":"    iteration."}],"source_content_type":"text/x-yaml","patch_set":7,"id":"c7727037_66d5a402","line":30,"in_reply_to":"d90d0b60_47a8e5c1","updated":"2026-09-23 13:29:15.000000000","message":"I suspected this behavior from previous tests but i took this case to double check and understand how this is implemented. We can discuss at PTG about how to handle strategies changes in updates.","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"}],"watcher/decision_engine/strategy/strategies/workload_stabilization.py":[{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"d2b071a3948de1532ef372256e62eab3bb6fb699","unresolved":true,"context_lines":[{"line_number":190,"context_line":"                    \"candidates (count set by least_loaded_count).\","},{"line_number":191,"context_line":"                    \"type\": \"string\","},{"line_number":192,"context_line":"                    \"enum\": [\"cycle\", \"retry\", \"fullsearch\", \"least_loaded\"],"},{"line_number":193,"context_line":"                    \"default\": \"retry\","},{"line_number":194,"context_line":"                },"},{"line_number":195,"context_line":"                \"least_loaded_count\": {"},{"line_number":196,"context_line":"                    \"description\": \"Number of least loaded destination hosts \""}],"source_content_type":"text/x-python","patch_set":4,"id":"05ce2cb4_91f618bf","line":193,"updated":"2026-06-10 10:15:38.000000000","message":"As follow up, in a different review, I would consider moving the default to least_loaded as it should provide better results.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":190,"context_line":"                    \"candidates (count set by least_loaded_count).\","},{"line_number":191,"context_line":"                    \"type\": \"string\","},{"line_number":192,"context_line":"                    \"enum\": [\"cycle\", \"retry\", \"fullsearch\", \"least_loaded\"],"},{"line_number":193,"context_line":"                    \"default\": \"retry\","},{"line_number":194,"context_line":"                },"},{"line_number":195,"context_line":"                \"least_loaded_count\": {"},{"line_number":196,"context_line":"                    \"description\": \"Number of least loaded destination hosts \""}],"source_content_type":"text/x-python","patch_set":4,"id":"6b7b523d_f324b506","line":193,"in_reply_to":"05ce2cb4_91f618bf","updated":"2026-09-17 18:07:05.000000000","message":"ya, i think that can even happne in the same cycle that its intoduced but we need to be carful that it does not change the behvior of continous audits that did not provide the parmater before.\n\nso before the defautl is chagned we should make sure all exsiting workload stablistaiton values have this set to a value which might need a db backfil.\n\nso +1 to changing the default in a sepreate followup patch","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"900c5411d0ed24680a68cb04920bad749bf10d22","unresolved":true,"context_lines":[{"line_number":190,"context_line":"                    \"candidates (count set by least_loaded_count).\","},{"line_number":191,"context_line":"                    \"type\": \"string\","},{"line_number":192,"context_line":"                    \"enum\": [\"cycle\", \"retry\", \"fullsearch\", \"least_loaded\"],"},{"line_number":193,"context_line":"                    \"default\": \"retry\","},{"line_number":194,"context_line":"                },"},{"line_number":195,"context_line":"                \"least_loaded_count\": {"},{"line_number":196,"context_line":"                    \"description\": \"Number of least loaded destination hosts \""}],"source_content_type":"text/x-python","patch_set":4,"id":"722aeab6_8c2ba4b5","line":193,"in_reply_to":"6b7b523d_f324b506","updated":"2026-09-23 11:06:47.000000000","message":"See may comment about how watcher manage ongoing continuous audits when there is a change in strategies parameters.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":true,"context_lines":[{"line_number":201,"context_line":"                    \"minimum\": 1,"},{"line_number":202,"context_line":"                    \"default\": 1,"},{"line_number":203,"context_line":"                },"},{"line_number":204,"context_line":"                \"least_loaded_percent\": {"},{"line_number":205,"context_line":"                    \"description\": \"Percentage of the lightest hosts (by \""},{"line_number":206,"context_line":"                    \"weighted load) to form the destination candidate pool \""},{"line_number":207,"context_line":"                    \"when host_choice is \u0027least_loaded\u0027. least_loaded_count \""},{"line_number":208,"context_line":"                    \"hosts are then randomly sampled from this pool. A value \""},{"line_number":209,"context_line":"                    \"of 50 means only the lightest half of available nodes \""},{"line_number":210,"context_line":"                    \"are eligible as destinations.\","},{"line_number":211,"context_line":"                    \"type\": \"number\","},{"line_number":212,"context_line":"                    \"minimum\": 1,"},{"line_number":213,"context_line":"                    \"maximum\": 100,"},{"line_number":214,"context_line":"                    \"default\": 50,"},{"line_number":215,"context_line":"                },"},{"line_number":216,"context_line":"                \"retry_count\": {"},{"line_number":217,"context_line":"                    \"description\": \"Count of random returned hosts\","},{"line_number":218,"context_line":"                    \"type\": \"number\","}],"source_content_type":"text/x-python","patch_set":4,"id":"d76f265f_fc3aba81","line":215,"range":{"start_line":204,"start_character":0,"end_line":215,"end_character":18},"updated":"2026-06-09 16:52:12.000000000","message":"What if the default behavior is to include in the least_loaded group all that are below the mean? That may adjust better that a specified percentage.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":201,"context_line":"                    \"minimum\": 1,"},{"line_number":202,"context_line":"                    \"default\": 1,"},{"line_number":203,"context_line":"                },"},{"line_number":204,"context_line":"                \"least_loaded_percent\": {"},{"line_number":205,"context_line":"                    \"description\": \"Percentage of the lightest hosts (by \""},{"line_number":206,"context_line":"                    \"weighted load) to form the destination candidate pool \""},{"line_number":207,"context_line":"                    \"when host_choice is \u0027least_loaded\u0027. least_loaded_count \""},{"line_number":208,"context_line":"                    \"hosts are then randomly sampled from this pool. A value \""},{"line_number":209,"context_line":"                    \"of 50 means only the lightest half of available nodes \""},{"line_number":210,"context_line":"                    \"are eligible as destinations.\","},{"line_number":211,"context_line":"                    \"type\": \"number\","},{"line_number":212,"context_line":"                    \"minimum\": 1,"},{"line_number":213,"context_line":"                    \"maximum\": 100,"},{"line_number":214,"context_line":"                    \"default\": 50,"},{"line_number":215,"context_line":"                },"},{"line_number":216,"context_line":"                \"retry_count\": {"},{"line_number":217,"context_line":"                    \"description\": \"Count of random returned hosts\","},{"line_number":218,"context_line":"                    \"type\": \"number\","}],"source_content_type":"text/x-python","patch_set":4,"id":"34dfb33c_be3ece72","line":215,"range":{"start_line":204,"start_character":0,"end_line":215,"end_character":18},"in_reply_to":"d76f265f_fc3aba81","updated":"2026-09-17 18:07:05.000000000","message":"sure that woudl work but in that case i woudl eigher remove the defautl entirly and have this default to None where we only do this percetnatge filtiering if you expclity opt in or even just make this a hard limit fo say 50 host not 50%\n\n\nwith that said we may be over thinkign this.\n\nwe coudl jsut make this an integre number of host ot check and set it to 50 hosts by default. for small cloud that will be all host for large cloud that still a representative subset.\n\nsoe call this something like host_limit instead and have it be a fixed amoutn of host to check\n\nalthernitively we coudl jsut remove this entirly for now and only add if if its a prefromacne issue","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"7d4aa4dd_3c943c06","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"updated":"2026-06-09 16:52:12.000000000","message":"This comment somehow affect this and https://review.opendev.org/c/openstack/watcher/+/988533/, which i\u0027ve tested together.\n\nWith the current implementation, i think this is probably the best we can do. The existing behavior of the strategy is:\n\n1. If std. dev is \u003e threshold, this method simulates migrations for all the VMs in all the nodes and stores the std dev after migraion as an individual migration, it\u0027s like calculating \"migration candidates\". When calculating following VM migration, it does not consider any previous one.\n\n2. do_execute take all the candidates by order, if it reduces SD, it adds a migration action, updates the model, updates the metrics dict and then iterates over the next one.\n\n3. When the resulting sd is below threshold, it exits as it reach the goal.\n\nI find this suboptimal as 1 is very expensive and many of the migration candidates created on it will not be used at all. Reducing the source nodes in simulate_migrations as done in https://review.opendev.org/c/openstack/watcher/+/988533 is a good step and improves the behavior, but I think merging 1 and 2 in a single step may would provide the best performance:\n\n1. If std. dev is \u003e threshold, the strategy get the node with highest utilization and instances on it (order tbd). For each one, it tries to migrate to the lowest used node (no random but deterministically). If it fits (otherwise, check next host) and std dev is reduced, the migration is added and the host_load saved as based for the next migration.  Then, the following instance in the same host is tried until the first case where sd is not decreasing or usage is \u003c threshold.\n\n2. Keep iterating on instances from the most_used to the least_used.\n\n3. When sd is \u003c threshold or all the vms in all the hosts with utilization \u003e mean are tried, exit.\n\nI\u0027m this would be more optimal.\n\nSaid that, this change together with https://review.opendev.org/c/openstack/watcher/+/988533 and the locking changes provides probably a good enough scalability and results. In my tests, 113 hosts and 15000 VMs and a well balanced cluster (it had to iterate over all the vms), it took 9 mins with default config. Setting source_nodes_percent to 50 using #988533, it reduced to ~ 5 minutes.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":34452,"name":"Joan Gilabert","display_name":"jgilaber","email":"jgilaber@redhat.com","username":"jgilaber"},"change_message_id":"cb4fac3ec83a39c95e56fdeed0ec0580c4b2d0d3","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"bb1de103_97956279","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"in_reply_to":"48386d87_f897049b","updated":"2026-07-27 11:09:34.000000000","message":"I think I agree with Doug\u0027s comment, for changes more profound that what is currently proposed, I feel like introducing a new strategy, with a clear spec is a better use of our efforts and time","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"ff35192a5c4aa017594190e7bca5fa4ddf39b338","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"48386d87_f897049b","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"in_reply_to":"7d4aa4dd_3c943c06","updated":"2026-06-11 11:36:55.000000000","message":"Yes, I had the same feeling while working in these improvements. A refactoring in the code can make this more efficient for sure, but it would also change how the strategy was originally implemented, so I am not sure if we should really go far on that. Both changes proposed here are reusing the original algorithym and trying optimize somehow by adding extra funcionality. We should discuss more about that with the team. \nThank you for taking a time and testing both changes!","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3f867bb2e608705613e26e9665fe6fd878c6c6e6","unresolved":false,"context_lines":[{"line_number":533,"context_line":"                # minus the current source), then randomly sample"},{"line_number":534,"context_line":"                # least_loaded_count hosts from that intersection."},{"line_number":535,"context_line":"                node_set \u003d set(nodes)"},{"line_number":536,"context_line":"                pool \u003d [h for h in _least_loaded_cache if h in node_set]"},{"line_number":537,"context_line":"                sample_size \u003d min(self.least_loaded_count, len(pool))"},{"line_number":538,"context_line":"                while True:"},{"line_number":539,"context_line":"                    yield random.sample(pool, sample_size)"}],"source_content_type":"text/x-python","patch_set":6,"id":"0539b73f_812cff25","line":536,"updated":"2026-09-15 18:26:00.000000000","message":"In the new least_loaded branch of yield_nodes, when the cached pool intersects the valid destinations down to zero hosts, the generator yields an empty list forever: pool \u003d [] makes sample_size \u003d min(count, 0) \u003d 0 and random.sample([], 0) returns [] (verified). In the consumer loop, the first eligible instance then never enters `for dst_host in next(node_list):`, so `sd_case` is never bound, and the pre-existing `if sd_case is None: continue` check at line 581 raises UnboundLocalError, aborting the audit. This is reachable whenever pool_size \u003d\u003d max(1, ceil(len(hosts) * least_loaded_percent / 100)) \u003d\u003d 1 and the single ranked-lightest host is processed as a source that hosts an eligible (ACTIVE/PAUSED, non-excluded) instance: e.g. default least_loaded_percent\u003d50 with a 2-node cluster, or least_loaded_percent\u003d10 on a 10-node cluster.\n\n**Severity**: WARNING | **Confidence**: 0.85\n\n**Impact**: An operator who selects host_choice\u003d\u0027least_loaded\u0027 gets a crashed audit (unhandled UnboundLocalError, no action plan) instead of a solution whenever the ranked-lightest host is the first source processed with migratable instances and the percent/count configuration yields a one-host pool. The regression a fix must prevent currently has no test coverage.\n\n**Suggestion**:\nGuard the empty intersection before sampling: skip the source when `not pool` (e.g. raise StopIteration / yield nothing once and `continue` past the instance loop), or initialize `sd_case \u003d None` immediately before the `for instance in ...` loop so line 581 is safe. Add a regression test that configures least_loaded so pool_size \u003d\u003d 1 (e.g. least_loaded_percent small relative to the host count) with the lightest node as source and asserts simulate_migrations returns without raising.","commit_id":"12ce1ec84df4a22a5a39e0263825259f6576ccd1"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"51dc6417658d6b934b383401b065f2d1d82da4ac","unresolved":false,"context_lines":[{"line_number":533,"context_line":"                # minus the current source), then randomly sample"},{"line_number":534,"context_line":"                # least_loaded_count hosts from that intersection."},{"line_number":535,"context_line":"                node_set \u003d set(nodes)"},{"line_number":536,"context_line":"                pool \u003d [h for h in _least_loaded_cache if h in node_set]"},{"line_number":537,"context_line":"                sample_size \u003d min(self.least_loaded_count, len(pool))"},{"line_number":538,"context_line":"                while True:"},{"line_number":539,"context_line":"                    yield random.sample(pool, sample_size)"}],"source_content_type":"text/x-python","patch_set":6,"id":"fee50a8a_f67517ed","line":536,"in_reply_to":"0539b73f_812cff25","updated":"2026-09-15 19:52:03.000000000","message":"Fixed in new PS","commit_id":"12ce1ec84df4a22a5a39e0263825259f6576ccd1"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":212,"context_line":"                    \"least_loaded_percent.\","},{"line_number":213,"context_line":"                    \"type\": \"number\","},{"line_number":214,"context_line":"                    \"minimum\": 1,"},{"line_number":215,"context_line":"                    \"default\": 1,"},{"line_number":216,"context_line":"                },"},{"line_number":217,"context_line":"                \"least_loaded_percent\": {"},{"line_number":218,"context_line":"                    \"description\": \"Percentage of the lightest hosts (by \""}],"source_content_type":"text/x-python","patch_set":7,"id":"57bd680f_5edfc428","line":215,"range":{"start_line":215,"start_character":31,"end_line":215,"end_character":32},"updated":"2026-09-17 18:07:05.000000000","message":"i would proably set this to 3 by the way\n\ni think we want some level of variance to avoid senting many vms to the same host","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"900c5411d0ed24680a68cb04920bad749bf10d22","unresolved":true,"context_lines":[{"line_number":212,"context_line":"                    \"least_loaded_percent.\","},{"line_number":213,"context_line":"                    \"type\": \"number\","},{"line_number":214,"context_line":"                    \"minimum\": 1,"},{"line_number":215,"context_line":"                    \"default\": 1,"},{"line_number":216,"context_line":"                },"},{"line_number":217,"context_line":"                \"least_loaded_percent\": {"},{"line_number":218,"context_line":"                    \"description\": \"Percentage of the lightest hosts (by \""}],"source_content_type":"text/x-python","patch_set":7,"id":"5a03e370_5cafc106","line":215,"range":{"start_line":215,"start_character":31,"end_line":215,"end_character":32},"in_reply_to":"57bd680f_5edfc428","updated":"2026-09-23 11:06:47.000000000","message":"+1","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":341,"context_line":"    def normalize_hosts_load(self, hosts):"},{"line_number":342,"context_line":"        normalized_hosts \u003d copy.deepcopy(hosts)"},{"line_number":343,"context_line":"        for host in normalized_hosts:"},{"line_number":344,"context_line":"            if \u0027instance_ram_usage\u0027 in normalized_hosts[host]:"},{"line_number":345,"context_line":"                node \u003d self.compute_model.get_node_by_uuid(host)"},{"line_number":346,"context_line":"                normalized_hosts[host][\u0027instance_ram_usage\u0027] /\u003d float("},{"line_number":347,"context_line":"                    node.memory"},{"line_number":348,"context_line":"                )"},{"line_number":349,"context_line":""},{"line_number":350,"context_line":"        return normalized_hosts"},{"line_number":351,"context_line":""},{"line_number":352,"context_line":"    def _calculate_weighted_loads(self, hosts):"}],"source_content_type":"text/x-python","patch_set":7,"id":"4e1b8acf_e45d2421","line":349,"range":{"start_line":344,"start_character":11,"end_line":349,"end_character":1},"updated":"2026-09-17 18:07:05.000000000","message":"shoudl this not at least look at cpu, and ram, and possibel disk?\n\nthe default metrics are\n\n\n                        \"instance_cpu_usage\": \"host_cpu_usage\",\n                        \"instance_ram_usage\": \"host_ram_usage\",\n                        \nso im not sure why we are not normalicign cpus as well","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"b6c532c01971bce665c439ad1983426b7cdf3740","unresolved":true,"context_lines":[{"line_number":511,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":512,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":513,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":514,"context_line":"            pool_size \u003d max("},{"line_number":515,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":516,"context_line":"            )"},{"line_number":517,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":518,"context_line":"        else:"},{"line_number":519,"context_line":"            _least_loaded_cache \u003d []"}],"source_content_type":"text/x-python","patch_set":7,"id":"bd635a9e_f41d90ee","line":516,"range":{"start_line":514,"start_character":11,"end_line":516,"end_character":13},"updated":"2026-09-17 18:07:05.000000000","message":"```suggestion\n            pool_size \u003d max(\n                1, self.host_limit\n            )\n```","commit_id":"b7268c88352be1dda6be2e200dd04c0b610bfcba"}],"watcher/tests/unit/decision_engine/strategy/strategies/test_workload_stabilization.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"f6ffbc2db25f7d7c82ebc02c0718af3a1418fca8","unresolved":false,"context_lines":[{"line_number":456,"context_line":"        loads \u003d [load for _, load in result]"},{"line_number":457,"context_line":"        self.assertEqual(loads, sorted(loads))"},{"line_number":458,"context_line":""},{"line_number":459,"context_line":"    def test_simulate_migrations_with_least_loaded(self):"},{"line_number":460,"context_line":"        # Verify that \u0027least_loaded\u0027 host_choice restricts migration"},{"line_number":461,"context_line":"        # destinations to exactly the nodes returned by get_least_loaded_hosts."},{"line_number":462,"context_line":"        #"}],"source_content_type":"text/x-python","patch_set":5,"id":"2ba0e9ca_e69e59e5","line":459,"updated":"2026-08-26 19:42:41.000000000","message":"test_simulate_migrations_with_least_loaded mocks get_least_loaded_hosts to return 2 hosts and sets least_loaded_count\u003d2, but least_loaded_percent stays at 50 from setUp. simulate_migrations then computes pool_size \u003d max(1, ceil(2*50/100)) \u003d 1 and caches only [\u0027Node_3\u0027], so sample_size \u003d min(2, 1) \u003d 1 and Node_4 can never be sampled. The final assertion assertIn(entry[\u0027host\u0027], {\u0027Node_3\u0027, \u0027Node_4\u0027}) therefore passes trivially, and the test comment (\u0027the two lightest destinations\u0027, \u0027every suggested migration must target one of those two nodes\u0027) describes behavior the test never produces.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: A regression in the sampling logic (e.g., sample_size always 1 regardless of least_loaded_count, or pool slicing ignoring its inputs) would not fail this test, and the misleading comment can lead future maintainers to wrong conclusions about the covered behavior.\n\n**Suggestion**:\nSet self.strategy.least_loaded_percent \u003d 100 in this test (or mock at least 4 ranked hosts) so the full mocked list forms the pool; seed random or patch random.sample so the assertion deterministically verifies destinations are drawn from the ranked pool in the requested count, and add a case asserting the count is capped when it exceeds the pool.","commit_id":"cebcf256655bdc5b1bfa3c7023abded20c4bb543"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3f867bb2e608705613e26e9665fe6fd878c6c6e6","unresolved":false,"context_lines":[{"line_number":493,"context_line":"        loads \u003d [load for _, load in result]"},{"line_number":494,"context_line":"        self.assertEqual(loads, sorted(loads))"},{"line_number":495,"context_line":""},{"line_number":496,"context_line":"    def test_simulate_migrations_with_least_loaded(self):"},{"line_number":497,"context_line":"        # Verify that \u0027least_loaded\u0027 host_choice restricts migration"},{"line_number":498,"context_line":"        # destinations to exactly the nodes returned by get_least_loaded_hosts."},{"line_number":499,"context_line":"        #"}],"source_content_type":"text/x-python","patch_set":6,"id":"f02ee415_fb03a938","line":496,"updated":"2026-09-15 18:26:00.000000000","message":"The test\u0027s comments state that Node_3 and Node_4 are pinned as the two least-loaded destinations and that \u0027every suggested migration must target one of those two nodes\u0027, but with setUp\u0027s least_loaded_percent \u003d 50 and a mocked 2-host ranking, simulate_migrations computes pool_size \u003d max(1, ceil(2*50/100)) \u003d 1, so _least_loaded_cache \u003d [\u0027Node_3\u0027] only. Node_4 can never be selected, and the pinned-set assertion would still pass if a regression dropped every destination except the single lightest host. The comment \u0027get_least_loaded_hosts must have been called (once per source node ...)\u0027 is also wrong: the ranking is pre-computed exactly once per simulate_migrations call (line 513).\n\n**Severity**: SUGGESTION | **Confidence**: 0.85\n\n**Impact**: Future maintainers reading the test will believe both lightest hosts are exercised as destinations and that the ranking is recomputed per source node; a regression that collapses the pool to one host, or that reintroduces per-iteration ranking, would pass this test while contradicting its documented intent.\n\n**Recommendation**:\nSet self.strategy.least_loaded_percent \u003d 100 inside the test so pool_size \u003d 2 and both pinned nodes are genuinely sampleable (or mock a larger ranking); correct the comments to state the ranking is computed once per simulate_migrations call; optionally assert mock_least.call_count \u003d\u003d 1.","commit_id":"12ce1ec84df4a22a5a39e0263825259f6576ccd1"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"51dc6417658d6b934b383401b065f2d1d82da4ac","unresolved":false,"context_lines":[{"line_number":493,"context_line":"        loads \u003d [load for _, load in result]"},{"line_number":494,"context_line":"        self.assertEqual(loads, sorted(loads))"},{"line_number":495,"context_line":""},{"line_number":496,"context_line":"    def test_simulate_migrations_with_least_loaded(self):"},{"line_number":497,"context_line":"        # Verify that \u0027least_loaded\u0027 host_choice restricts migration"},{"line_number":498,"context_line":"        # destinations to exactly the nodes returned by get_least_loaded_hosts."},{"line_number":499,"context_line":"        #"}],"source_content_type":"text/x-python","patch_set":6,"id":"bd920a88_138b05f2","line":496,"in_reply_to":"f02ee415_fb03a938","updated":"2026-09-15 19:52:03.000000000","message":"Fixed in new PS","commit_id":"12ce1ec84df4a22a5a39e0263825259f6576ccd1"}]}
