portacode 1.4.17.dev1__py3-none-any.whl → 1.4.17.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
portacode/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.4.17.dev1'
32
- __version_tuple__ = version_tuple = (1, 4, 17, 'dev1')
31
+ __version__ = version = '1.4.17.dev3'
32
+ __version_tuple__ = version_tuple = (1, 4, 17, 'dev3')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -49,6 +49,7 @@ _MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
49
49
  _MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
50
50
  _MANAGED_CONTAINERS_CACHE_LOCK = threading.Lock()
51
51
  _CAPACITY_LOCK = threading.Lock()
52
+ _PENDING_ALLOCATIONS = {"ram_mib": 0.0, "disk_gib": 0.0, "cpu_share": 0.0}
52
53
  TEMPLATES_REFRESH_INTERVAL_S = 300
53
54
 
54
55
  ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
@@ -421,7 +422,14 @@ def _to_mib(value: Any) -> float:
421
422
 
422
423
 
423
424
  def _pick_container_ram_mib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
424
- for candidate in (cfg.get("memory"), entry.get("maxmem"), entry.get("mem")):
425
+ """
426
+ Proxmox config `memory` is already MiB for both LXC and QEMU.
427
+ Fall back to list fields (bytes) only when it is absent/zero.
428
+ """
429
+ mem_cfg = _safe_float(cfg.get("memory"))
430
+ if mem_cfg:
431
+ return mem_cfg
432
+ for candidate in (entry.get("maxmem"), entry.get("mem")):
425
433
  ram = _to_mib(candidate)
426
434
  if ram:
427
435
  return ram
@@ -721,6 +729,13 @@ def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[st
721
729
  "template": record.get("template") if record else None,
722
730
  "created_at": record.get("created_at") if record else None,
723
731
  }
732
+ # Fallback to recorded specs if live probing returned zeros (e.g., just-created CT).
733
+ if not merged["ram_mib"] and record:
734
+ merged["ram_mib"] = record.get("ram_mib") or merged["ram_mib"]
735
+ if not merged["disk_gib"] and record:
736
+ merged["disk_gib"] = record.get("disk_gib") or merged["disk_gib"]
737
+ if not merged["cpu_share"] and record:
738
+ merged["cpu_share"] = record.get("cpus") or merged["cpu_share"]
724
739
  managed_entries.append(merged)
725
740
  else:
726
741
  unmanaged_entries.append(base_entry)
@@ -783,20 +798,24 @@ def _compute_free_resources(summary: Dict[str, Any]) -> Dict[str, float]:
783
798
  free_ram = None
784
799
  free_disk = None
785
800
  free_cpu = None
801
+ with _CAPACITY_LOCK:
802
+ pending_ram = float(_PENDING_ALLOCATIONS["ram_mib"])
803
+ pending_disk = float(_PENDING_ALLOCATIONS["disk_gib"])
804
+ pending_cpu = float(_PENDING_ALLOCATIONS["cpu_share"])
786
805
  host_ram = summary.get("host_total_ram_mib")
787
806
  alloc_ram = summary.get("allocated_ram_mib")
788
807
  if host_ram is not None and alloc_ram is not None:
789
- free_ram = max(float(host_ram) - float(alloc_ram), 0.0)
808
+ free_ram = max(float(host_ram) - float(alloc_ram) - pending_ram, 0.0)
790
809
 
791
810
  host_disk = summary.get("host_total_disk_gib")
792
811
  alloc_disk = summary.get("allocated_disk_gib")
793
812
  if host_disk is not None and alloc_disk is not None:
794
- free_disk = max(float(host_disk) - float(alloc_disk), 0.0)
813
+ free_disk = max(float(host_disk) - float(alloc_disk) - pending_disk, 0.0)
795
814
 
796
815
  host_cpu = summary.get("host_total_cpu_cores")
797
816
  alloc_cpu = summary.get("allocated_cpu_share")
798
817
  if host_cpu is not None and alloc_cpu is not None:
799
- free_cpu = max(float(host_cpu) - float(alloc_cpu), 0.0)
818
+ free_cpu = max(float(host_cpu) - float(alloc_cpu) - pending_cpu, 0.0)
800
819
 
801
820
  return {
802
821
  "ram_mib": free_ram,
@@ -1155,21 +1174,30 @@ def _start_container(proxmox: Any, node: str, vmid: int) -> Tuple[Dict[str, Any]
1155
1174
  return status, 0.0
1156
1175
 
1157
1176
  # Validate capacity using the same math as the dashboard and serialize allocation.
1177
+ summary = _get_managed_containers_summary(force=True)
1178
+ cfg = proxmox.nodes(node).lxc(vmid).config.get()
1179
+ payload = {
1180
+ "ram_mib": _to_mib(cfg.get("memory")),
1181
+ "disk_gib": _pick_container_disk_gib("lxc", cfg, {"rootfs": cfg.get("rootfs")}),
1182
+ "cpus": _pick_container_cpu_share("lxc", cfg, {}),
1183
+ }
1158
1184
  with _CAPACITY_LOCK:
1159
- summary = _get_managed_containers_summary(force=True)
1160
- cfg = proxmox.nodes(node).lxc(vmid).config.get()
1161
- payload = {
1162
- "ram_mib": _to_mib(cfg.get("memory")),
1163
- "disk_gib": _pick_container_disk_gib("lxc", cfg, {"rootfs": cfg.get("rootfs")}),
1164
- "cpus": _pick_container_cpu_share("lxc", cfg, {}),
1165
- }
1166
1185
  try:
1167
1186
  _assert_capacity_for_payload(payload, summary)
1168
1187
  except RuntimeError as exc:
1169
1188
  raise RuntimeError(f"Not enough resources to start container {vmid}: {exc}") from exc
1189
+ _PENDING_ALLOCATIONS["ram_mib"] += float(payload["ram_mib"] or 0)
1190
+ _PENDING_ALLOCATIONS["disk_gib"] += float(payload["disk_gib"] or 0)
1191
+ _PENDING_ALLOCATIONS["cpu_share"] += float(payload["cpus"] or 0)
1170
1192
 
1171
- upid = proxmox.nodes(node).lxc(vmid).status.start.post()
1172
- return _wait_for_task(proxmox, node, upid)
1193
+ try:
1194
+ upid = proxmox.nodes(node).lxc(vmid).status.start.post()
1195
+ return _wait_for_task(proxmox, node, upid)
1196
+ finally:
1197
+ with _CAPACITY_LOCK:
1198
+ _PENDING_ALLOCATIONS["ram_mib"] -= float(payload["ram_mib"] or 0)
1199
+ _PENDING_ALLOCATIONS["disk_gib"] -= float(payload["disk_gib"] or 0)
1200
+ _PENDING_ALLOCATIONS["cpu_share"] -= float(payload["cpus"] or 0)
1173
1201
 
1174
1202
 
1175
1203
  def _stop_container(proxmox: Any, node: str, vmid: int) -> Tuple[Dict[str, Any], float]:
@@ -1896,22 +1924,31 @@ class CreateProxmoxContainerHandler(SyncHandler):
1896
1924
  _validate_environment,
1897
1925
  )
1898
1926
 
1899
- def _create_container():
1900
- proxmox = _connect_proxmox(config)
1901
- node = config.get("node") or DEFAULT_NODE_NAME
1902
- payload = _build_container_payload(message, config)
1903
- payload["cpulimit"] = float(payload["cpus"])
1904
- payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1905
- payload["memory"] = int(payload["ram_mib"])
1906
- payload["node"] = node
1907
- # Validate against current free resources (same math as dashboard charts) with serialization.
1908
- with _CAPACITY_LOCK:
1909
- summary = _get_managed_containers_summary(force=True)
1910
- try:
1911
- _assert_capacity_for_payload(payload, summary)
1912
- except RuntimeError as exc:
1913
- raise RuntimeError(f"Not enough resources to create the container safely: {exc}") from exc
1914
- vmid, _ = _instantiate_container(proxmox, node, payload)
1927
+ def _create_container():
1928
+ proxmox = _connect_proxmox(config)
1929
+ node = config.get("node") or DEFAULT_NODE_NAME
1930
+ payload = _build_container_payload(message, config)
1931
+ payload["cpulimit"] = float(payload["cpus"])
1932
+ payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1933
+ payload["memory"] = int(payload["ram_mib"])
1934
+ payload["node"] = node
1935
+ # Validate against current free resources (same math as dashboard charts) and place a short-lived reservation.
1936
+ summary = _get_managed_containers_summary(force=True)
1937
+ with _CAPACITY_LOCK:
1938
+ try:
1939
+ _assert_capacity_for_payload(payload, summary)
1940
+ except RuntimeError as exc:
1941
+ raise RuntimeError(f"Not enough resources to create the container safely: {exc}") from exc
1942
+ _PENDING_ALLOCATIONS["ram_mib"] += float(payload["ram_mib"] or 0)
1943
+ _PENDING_ALLOCATIONS["disk_gib"] += float(payload["disk_gib"] or 0)
1944
+ _PENDING_ALLOCATIONS["cpu_share"] += float(payload["cpus"] or 0)
1945
+ try:
1946
+ vmid, _ = _instantiate_container(proxmox, node, payload)
1947
+ finally:
1948
+ with _CAPACITY_LOCK:
1949
+ _PENDING_ALLOCATIONS["ram_mib"] -= float(payload["ram_mib"] or 0)
1950
+ _PENDING_ALLOCATIONS["disk_gib"] -= float(payload["disk_gib"] or 0)
1951
+ _PENDING_ALLOCATIONS["cpu_share"] -= float(payload["cpus"] or 0)
1915
1952
  logger.debug(
1916
1953
  "Provisioning container node=%s template=%s ram=%s cpu=%s storage=%s",
1917
1954
  node,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.17.dev1
3
+ Version: 1.4.17.dev3
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=30JbXF45c9c1UEsvLScl1Hcz0g1qPk9RGMCBErXUK0c,719
4
+ portacode/_version.py,sha256=16HDRPCTtR6orWusoLn2afHMuqIOPfcRoxTVdLzEYl0,719
5
5
  portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
@@ -22,7 +22,7 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
22
22
  portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
23
23
  portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
24
24
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
25
- portacode/connection/handlers/proxmox_infra.py,sha256=afndfVsjcHWHsa8F2PkRnHmc67c39k5VMmJeUoSSnok,87601
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=Tuugl7DRJ_oejKQO96A722N4Jt5S-RBkz73LIWDeKEc,89543
26
26
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
27
27
  portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
28
28
  portacode/connection/handlers/system_handlers.py,sha256=fr12QpOr_Z8KYGUU-AYrTQwRPAcrLK85hvj3SEq1Kw8,14757
@@ -65,7 +65,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
65
65
  portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
66
66
  portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
67
67
  portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
68
- portacode-1.4.17.dev1.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.17.dev3.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
69
69
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
70
70
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
71
71
  test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
@@ -91,8 +91,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
91
91
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
92
92
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
93
93
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
94
- portacode-1.4.17.dev1.dist-info/METADATA,sha256=fiXLmOIToLe48UzncEAQCZnpib1ITZpl0gS06M5fmFA,13051
95
- portacode-1.4.17.dev1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.17.dev1.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.17.dev1.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.17.dev1.dist-info/RECORD,,
94
+ portacode-1.4.17.dev3.dist-info/METADATA,sha256=2SfZxixbECm7f6jilfpD9-8Oal2HIpqK0T07xnxEVa4,13051
95
+ portacode-1.4.17.dev3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.17.dev3.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.17.dev3.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.17.dev3.dist-info/RECORD,,