test_ephemeral_ports.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from pathlib import Path
  2. from gpustack.utils import ephemeral_ports
  3. def test_parse_ranges_mixed():
  4. result = ephemeral_ports._parse_ranges("80, 8000-8010,\t9000")
  5. assert result == [(80, 80), (8000, 8010), (9000, 9000)]
  6. def test_parse_ranges_empty():
  7. assert ephemeral_ports._parse_ranges("") == []
  8. def test_merge_adjacent_and_overlap():
  9. merged = ephemeral_ports._merge(
  10. [(40000, 40063), (41000, 41999), (41500, 42000), (42001, 42010)]
  11. )
  12. assert merged == [(40000, 40063), (41000, 42010)]
  13. def test_format_ranges_single_and_range():
  14. assert ephemeral_ports._format_ranges([(80, 80), (8000, 8010)]) == "80,8000-8010"
  15. def test_covered_by_true():
  16. assert ephemeral_ports._covered_by((41005, 41500), [(41000, 41999)])
  17. def test_covered_by_false_partial():
  18. assert not ephemeral_ports._covered_by((41000, 42500), [(41000, 41999)])
  19. def test_ensure_noop_when_no_overlap(monkeypatch, tmp_path: Path):
  20. ephemeral = tmp_path / "ip_local_port_range"
  21. ephemeral.write_text("32768\t60999\n")
  22. reserved = tmp_path / "ip_local_reserved_ports"
  23. reserved.write_text("")
  24. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  25. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  26. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  27. ephemeral_ports.ensure_reserved_against_ephemeral(
  28. [("ray_port_range", (61000, 61999))]
  29. )
  30. assert reserved.read_text() == ""
  31. def test_ensure_writes_reservation_on_conflict(monkeypatch, tmp_path: Path):
  32. ephemeral = tmp_path / "ip_local_port_range"
  33. ephemeral.write_text("32768\t60999\n")
  34. reserved = tmp_path / "ip_local_reserved_ports"
  35. reserved.write_text("")
  36. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  37. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  38. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  39. ephemeral_ports.ensure_reserved_against_ephemeral(
  40. [
  41. ("service_port_range", (40000, 40063)),
  42. ("ray_port_range", (41000, 41999)),
  43. ]
  44. )
  45. assert reserved.read_text() == "40000-40063,41000-41999"
  46. def test_ensure_merges_with_existing_reservation(monkeypatch, tmp_path: Path):
  47. ephemeral = tmp_path / "ip_local_port_range"
  48. ephemeral.write_text("32768 60999")
  49. reserved = tmp_path / "ip_local_reserved_ports"
  50. reserved.write_text("12345,40000-40100")
  51. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  52. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  53. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  54. ephemeral_ports.ensure_reserved_against_ephemeral(
  55. [("ray_port_range", (41000, 41999))]
  56. )
  57. assert reserved.read_text() == "12345,40000-40100,41000-41999"
  58. def test_ensure_noop_when_already_covered(monkeypatch, tmp_path: Path):
  59. ephemeral = tmp_path / "ip_local_port_range"
  60. ephemeral.write_text("32768 60999")
  61. reserved = tmp_path / "ip_local_reserved_ports"
  62. reserved.write_text("40000-42000")
  63. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  64. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  65. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  66. ephemeral_ports.ensure_reserved_against_ephemeral(
  67. [
  68. ("service_port_range", (40000, 40063)),
  69. ("ray_port_range", (41000, 41999)),
  70. ]
  71. )
  72. assert reserved.read_text() == "40000-42000"
  73. def test_ensure_aborts_on_unparseable_reserved(monkeypatch, tmp_path: Path, caplog):
  74. ephemeral = tmp_path / "ip_local_port_range"
  75. ephemeral.write_text("32768 60999")
  76. reserved = tmp_path / "ip_local_reserved_ports"
  77. reserved.write_text("not-a-port-list")
  78. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  79. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  80. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  81. with caplog.at_level("WARNING"):
  82. ephemeral_ports.ensure_reserved_against_ephemeral(
  83. [("ray_port_range", (41000, 41999))]
  84. )
  85. assert reserved.read_text() == "not-a-port-list"
  86. assert any("Cannot parse" in rec.message for rec in caplog.records)
  87. def test_parse_ranges_whitespace_separated():
  88. assert ephemeral_ports._parse_ranges("40000-40063 41000-41999") == [
  89. (40000, 40063),
  90. (41000, 41999),
  91. ]
  92. def test_ensure_warns_when_write_fails(monkeypatch, tmp_path: Path, caplog):
  93. ephemeral = tmp_path / "ip_local_port_range"
  94. ephemeral.write_text("32768 60999")
  95. reserved = tmp_path / "ip_local_reserved_ports"
  96. reserved.write_text("")
  97. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  98. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  99. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  100. original_write_text = Path.write_text
  101. def _fail_write(self, *args, **kwargs):
  102. if self == reserved:
  103. raise PermissionError("read-only")
  104. return original_write_text(self, *args, **kwargs)
  105. monkeypatch.setattr(Path, "write_text", _fail_write)
  106. with caplog.at_level("WARNING"):
  107. ephemeral_ports.ensure_reserved_against_ephemeral(
  108. [("ray_port_range", (41000, 41999))]
  109. )
  110. assert any(
  111. "overlap the kernel ephemeral port" in rec.message for rec in caplog.records
  112. )
  113. def test_ensure_skips_when_env_opt_out(monkeypatch, tmp_path: Path):
  114. ephemeral = tmp_path / "ip_local_port_range"
  115. ephemeral.write_text("32768 60999")
  116. reserved = tmp_path / "ip_local_reserved_ports"
  117. reserved.write_text("")
  118. monkeypatch.setattr(ephemeral_ports, "_LOCAL_PORT_RANGE_PATH", ephemeral)
  119. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  120. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "linux")
  121. monkeypatch.setattr(ephemeral_ports.envs, "SKIP_RESERVE_EPHEMERAL_PORTS", True)
  122. ephemeral_ports.ensure_reserved_against_ephemeral(
  123. [("ray_port_range", (41000, 41999))]
  124. )
  125. assert reserved.read_text() == ""
  126. def test_ensure_skips_non_linux(monkeypatch, tmp_path: Path):
  127. reserved = tmp_path / "ip_local_reserved_ports"
  128. reserved.write_text("")
  129. monkeypatch.setattr(ephemeral_ports, "_RESERVED_PORTS_PATH", reserved)
  130. monkeypatch.setattr(ephemeral_ports.platform, "system", lambda: "darwin")
  131. ephemeral_ports.ensure_reserved_against_ephemeral(
  132. [("ray_port_range", (41000, 41999))]
  133. )
  134. assert reserved.read_text() == ""