diff --git a/bin/test_yc_ip_hunter.py b/bin/test_yc_ip_hunter.py index e99a7fb..1d5f34d 100644 --- a/bin/test_yc_ip_hunter.py +++ b/bin/test_yc_ip_hunter.py @@ -810,6 +810,34 @@ def test_project_mode_success_blocks_after_auto_isolation(self): self.assertEqual(hunter.state["auto_protected_folder_ids"], ["folder-1"]) self.assertEqual(hunter.state["status"], "blocked_by_project_isolation") + def test_hybrid_continue_success_returns_ok_when_iteration_limit_ends(self): + hunter = object.__new__(yc.IpHunter) + hunter.config = { + "continue_after_success": True, + "max_iterations": 1, + "cloud_iteration_sleep_seconds": 0, + } + hunter.state = {} + hunter.persist_state = lambda: None + hunter.notify_success = lambda result: None + hunter.iteration_numbers = lambda max_iterations: iter([1]) + hunter.ensure_hybrid_address_scope = lambda iteration: ("cloud-1", "folder-1") + hunter.can_delete_hybrid_cloud = lambda cloud_id, folder_id: True + hunter.run_address_rotation_in_cloud = lambda cloud_id, folder_id, iteration: yc.AttemptResult( + ip="198.51.100.10", + zone="ru-central1-a", + address_id="addr-1", + cloud_id=cloud_id, + folder_id=folder_id, + ) + sleeps = [] + hunter.sleep_backoff = lambda seconds: sleeps.append(seconds) + + self.assertEqual(hunter.run_hybrid_rotation(), 0) + self.assertEqual(hunter.state["success"]["ip"], "198.51.100.10") + self.assertEqual(hunter.state["cloud_recreations_done"], 1) + self.assertEqual(sleeps, [0]) + def test_telegram_enabled_inside_disabled_parent_still_sends(self): hunter = object.__new__(yc.IpHunter) hunter.config = { diff --git a/bin/yc_ip_hunter.py b/bin/yc_ip_hunter.py index ec05356..d8358cb 100644 --- a/bin/yc_ip_hunter.py +++ b/bin/yc_ip_hunter.py @@ -933,6 +933,7 @@ def run_folder_rotation(self) -> int: base_backoff = float(self.config.get("cooldown_seconds", 15)) max_backoff = float(self.config.get("backoff_max_seconds", 120)) backoff = base_backoff + reserved_any = False for iteration in self.iteration_numbers(max_iterations): folder_id = "" @@ -961,6 +962,7 @@ def run_folder_rotation(self) -> int: raise self.step_error("folder rotation", exc) from exc if result: + reserved_any = True self.save_success(result) LOGGER.info( "Reserved target IP %s in cloud=%s folder=%s address=%s.", @@ -983,6 +985,12 @@ def run_folder_rotation(self) -> int: self.sleep_backoff(float(self.config.get("iteration_sleep_seconds", 10))) backoff = base_backoff + if reserved_any: + LOGGER.info( + "Search finished after max_iterations=%s with at least one reserved target IP.", + max_iterations, + ) + return 0 LOGGER.error("No requested IP reserved after max_iterations=%s.", max_iterations) return 2 @@ -991,6 +999,7 @@ def run_cloud_rotation(self) -> int: base_backoff = float(self.config.get("cooldown_seconds", 15)) max_backoff = float(self.config.get("backoff_max_seconds", 240)) backoff = base_backoff + reserved_any = False for iteration in self.iteration_numbers(max_iterations): LOGGER.info("Cloud rotation iteration %s/%s.", iteration, self.iteration_limit_label(max_iterations)) @@ -1004,6 +1013,7 @@ def run_cloud_rotation(self) -> int: cloud_id, folder_id = self.create_cloud_cycle(iteration) result = self.allocate_cloud_batch(cloud_id, folder_id, iteration) if result: + reserved_any = True self.save_success(result) LOGGER.info( "Reserved target IP %s in cloud=%s folder=%s address=%s.", @@ -1044,6 +1054,12 @@ def run_cloud_rotation(self) -> int: return 2 raise self.step_error("cloud rotation", exc) from exc + if reserved_any: + LOGGER.info( + "Search finished after max_iterations=%s with at least one reserved target IP.", + max_iterations, + ) + return 0 LOGGER.error("No requested IP reserved after max_iterations=%s.", max_iterations) return 2 @@ -1053,6 +1069,7 @@ def run_hybrid_rotation(self) -> int: max_backoff = float(self.config.get("backoff_max_seconds", 240)) backoff = base_backoff use_existing_scope = True + reserved_any = False for iteration in self.iteration_numbers(max_iterations): LOGGER.info( @@ -1075,6 +1092,7 @@ def run_hybrid_rotation(self) -> int: managed_cloud = True result = self.run_address_rotation_in_cloud(cloud_id, folder_id, iteration) if result: + reserved_any = True self.save_success(result) LOGGER.info( "Reserved target IP %s in cloud=%s folder=%s address=%s.", @@ -1147,6 +1165,12 @@ def run_hybrid_rotation(self) -> int: continue raise self.step_error("hybrid rotation", exc) from exc + if reserved_any: + LOGGER.info( + "Search finished after max_iterations=%s with at least one reserved target IP.", + max_iterations, + ) + return 0 LOGGER.error("No requested IP reserved after max_iterations=%s.", max_iterations) return 2