portacode 1.4.17.dev0__py3-none-any.whl → 1.4.17.dev2__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.dev0'
32
- __version_tuple__ = version_tuple = (1, 4, 17, 'dev0')
31
+ __version__ = version = '1.4.17.dev2'
32
+ __version_tuple__ = version_tuple = (1, 4, 17, 'dev2')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -48,6 +48,8 @@ UNIT_DIR = Path("/etc/systemd/system")
48
48
  _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
+ _CAPACITY_LOCK = threading.Lock()
52
+ _PENDING_ALLOCATIONS = {"ram_mib": 0.0, "disk_gib": 0.0, "cpu_share": 0.0}
51
53
  TEMPLATES_REFRESH_INTERVAL_S = 300
52
54
 
53
55
  ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
@@ -720,6 +722,13 @@ def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[st
720
722
  "template": record.get("template") if record else None,
721
723
  "created_at": record.get("created_at") if record else None,
722
724
  }
725
+ # Fallback to recorded specs if live probing returned zeros (e.g., just-created CT).
726
+ if not merged["ram_mib"] and record:
727
+ merged["ram_mib"] = record.get("ram_mib") or merged["ram_mib"]
728
+ if not merged["disk_gib"] and record:
729
+ merged["disk_gib"] = record.get("disk_gib") or merged["disk_gib"]
730
+ if not merged["cpu_share"] and record:
731
+ merged["cpu_share"] = record.get("cpus") or merged["cpu_share"]
723
732
  managed_entries.append(merged)
724
733
  else:
725
734
  unmanaged_entries.append(base_entry)
@@ -782,20 +791,24 @@ def _compute_free_resources(summary: Dict[str, Any]) -> Dict[str, float]:
782
791
  free_ram = None
783
792
  free_disk = None
784
793
  free_cpu = None
794
+ with _CAPACITY_LOCK:
795
+ pending_ram = float(_PENDING_ALLOCATIONS["ram_mib"])
796
+ pending_disk = float(_PENDING_ALLOCATIONS["disk_gib"])
797
+ pending_cpu = float(_PENDING_ALLOCATIONS["cpu_share"])
785
798
  host_ram = summary.get("host_total_ram_mib")
786
799
  alloc_ram = summary.get("allocated_ram_mib")
787
800
  if host_ram is not None and alloc_ram is not None:
788
- free_ram = max(float(host_ram) - float(alloc_ram), 0.0)
801
+ free_ram = max(float(host_ram) - float(alloc_ram) - pending_ram, 0.0)
789
802
 
790
803
  host_disk = summary.get("host_total_disk_gib")
791
804
  alloc_disk = summary.get("allocated_disk_gib")
792
805
  if host_disk is not None and alloc_disk is not None:
793
- free_disk = max(float(host_disk) - float(alloc_disk), 0.0)
806
+ free_disk = max(float(host_disk) - float(alloc_disk) - pending_disk, 0.0)
794
807
 
795
808
  host_cpu = summary.get("host_total_cpu_cores")
796
809
  alloc_cpu = summary.get("allocated_cpu_share")
797
810
  if host_cpu is not None and alloc_cpu is not None:
798
- free_cpu = max(float(host_cpu) - float(alloc_cpu), 0.0)
811
+ free_cpu = max(float(host_cpu) - float(alloc_cpu) - pending_cpu, 0.0)
799
812
 
800
813
  return {
801
814
  "ram_mib": free_ram,
@@ -1153,7 +1166,7 @@ def _start_container(proxmox: Any, node: str, vmid: int) -> Tuple[Dict[str, Any]
1153
1166
  logger.info("Container %s already running (%ss)", vmid, uptime)
1154
1167
  return status, 0.0
1155
1168
 
1156
- # Validate capacity using the same math as the dashboard.
1169
+ # Validate capacity using the same math as the dashboard and serialize allocation.
1157
1170
  summary = _get_managed_containers_summary(force=True)
1158
1171
  cfg = proxmox.nodes(node).lxc(vmid).config.get()
1159
1172
  payload = {
@@ -1161,13 +1174,23 @@ def _start_container(proxmox: Any, node: str, vmid: int) -> Tuple[Dict[str, Any]
1161
1174
  "disk_gib": _pick_container_disk_gib("lxc", cfg, {"rootfs": cfg.get("rootfs")}),
1162
1175
  "cpus": _pick_container_cpu_share("lxc", cfg, {}),
1163
1176
  }
1164
- try:
1165
- _assert_capacity_for_payload(payload, summary)
1166
- except RuntimeError as exc:
1167
- raise RuntimeError(f"Not enough resources to start container {vmid}: {exc}") from exc
1177
+ with _CAPACITY_LOCK:
1178
+ try:
1179
+ _assert_capacity_for_payload(payload, summary)
1180
+ except RuntimeError as exc:
1181
+ raise RuntimeError(f"Not enough resources to start container {vmid}: {exc}") from exc
1182
+ _PENDING_ALLOCATIONS["ram_mib"] += float(payload["ram_mib"] or 0)
1183
+ _PENDING_ALLOCATIONS["disk_gib"] += float(payload["disk_gib"] or 0)
1184
+ _PENDING_ALLOCATIONS["cpu_share"] += float(payload["cpus"] or 0)
1168
1185
 
1169
- upid = proxmox.nodes(node).lxc(vmid).status.start.post()
1170
- return _wait_for_task(proxmox, node, upid)
1186
+ try:
1187
+ upid = proxmox.nodes(node).lxc(vmid).status.start.post()
1188
+ return _wait_for_task(proxmox, node, upid)
1189
+ finally:
1190
+ with _CAPACITY_LOCK:
1191
+ _PENDING_ALLOCATIONS["ram_mib"] -= float(payload["ram_mib"] or 0)
1192
+ _PENDING_ALLOCATIONS["disk_gib"] -= float(payload["disk_gib"] or 0)
1193
+ _PENDING_ALLOCATIONS["cpu_share"] -= float(payload["cpus"] or 0)
1171
1194
 
1172
1195
 
1173
1196
  def _stop_container(proxmox: Any, node: str, vmid: int) -> Tuple[Dict[str, Any], float]:
@@ -1894,20 +1917,31 @@ class CreateProxmoxContainerHandler(SyncHandler):
1894
1917
  _validate_environment,
1895
1918
  )
1896
1919
 
1897
- def _create_container():
1898
- proxmox = _connect_proxmox(config)
1899
- node = config.get("node") or DEFAULT_NODE_NAME
1900
- payload = _build_container_payload(message, config)
1901
- payload["cpulimit"] = float(payload["cpus"])
1902
- payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1903
- payload["memory"] = int(payload["ram_mib"])
1904
- payload["node"] = node
1905
- # Validate against current free resources (same math as dashboard charts).
1906
- summary = _get_managed_containers_summary(force=True)
1907
- try:
1908
- _assert_capacity_for_payload(payload, summary)
1909
- except RuntimeError as exc:
1910
- raise RuntimeError(f"Not enough resources to create the container safely: {exc}") from exc
1920
+ def _create_container():
1921
+ proxmox = _connect_proxmox(config)
1922
+ node = config.get("node") or DEFAULT_NODE_NAME
1923
+ payload = _build_container_payload(message, config)
1924
+ payload["cpulimit"] = float(payload["cpus"])
1925
+ payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
1926
+ payload["memory"] = int(payload["ram_mib"])
1927
+ payload["node"] = node
1928
+ # Validate against current free resources (same math as dashboard charts) and place a short-lived reservation.
1929
+ summary = _get_managed_containers_summary(force=True)
1930
+ with _CAPACITY_LOCK:
1931
+ try:
1932
+ _assert_capacity_for_payload(payload, summary)
1933
+ except RuntimeError as exc:
1934
+ raise RuntimeError(f"Not enough resources to create the container safely: {exc}") from exc
1935
+ _PENDING_ALLOCATIONS["ram_mib"] += float(payload["ram_mib"] or 0)
1936
+ _PENDING_ALLOCATIONS["disk_gib"] += float(payload["disk_gib"] or 0)
1937
+ _PENDING_ALLOCATIONS["cpu_share"] += float(payload["cpus"] or 0)
1938
+ try:
1939
+ vmid, _ = _instantiate_container(proxmox, node, payload)
1940
+ finally:
1941
+ with _CAPACITY_LOCK:
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)
1911
1945
  logger.debug(
1912
1946
  "Provisioning container node=%s template=%s ram=%s cpu=%s storage=%s",
1913
1947
  node,
@@ -1916,7 +1950,6 @@ class CreateProxmoxContainerHandler(SyncHandler):
1916
1950
  payload["cpus"],
1917
1951
  payload["storage"],
1918
1952
  )
1919
- vmid, _ = _instantiate_container(proxmox, node, payload)
1920
1953
  payload["vmid"] = vmid
1921
1954
  payload["created_at"] = datetime.utcnow().isoformat() + "Z"
1922
1955
  payload["status"] = "creating"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.17.dev0
3
+ Version: 1.4.17.dev2
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=bQ-FAL2ble5i7ZHjLXfiacqJWiDcpjxBVD73svWzbjg,719
4
+ portacode/_version.py,sha256=tY1iaGi0436VvMeNiABna_erno3lFewWo4mlsbvOmsg,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=GjGB0BICYR5eI-tUYwg7p2qnBWvLu_UcMshP5XTTLr0,87397
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=_v-qj-7zupk6egmed1AN_6iEhpL2k5hVa0q6-iZyK2I,89330
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.dev0.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.17.dev2.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.dev0.dist-info/METADATA,sha256=F-YQ9c2OK7Ck8xlpNpfdypteq_TEfZMKpB-HGLVhllg,13051
95
- portacode-1.4.17.dev0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.17.dev0.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.17.dev0.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.17.dev0.dist-info/RECORD,,
94
+ portacode-1.4.17.dev2.dist-info/METADATA,sha256=dX2o0Oj7K-LojM-kwShhU7XEWqV-Lw30xCImcbfc-wU,13051
95
+ portacode-1.4.17.dev2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.17.dev2.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.17.dev2.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.17.dev2.dist-info/RECORD,,