boxd 0.1.2.dev13__tar.gz → 0.1.3__tar.gz
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.
- {boxd-0.1.2.dev13/src/boxd.egg-info → boxd-0.1.3}/PKG-INFO +1 -1
- {boxd-0.1.2.dev13 → boxd-0.1.3}/pyproject.toml +1 -1
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_sync.py +10 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/box.py +46 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3/src/boxd.egg-info}/PKG-INFO +1 -1
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_e2e.py +4 -8
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_e2e_v2.py +3 -7
- {boxd-0.1.2.dev13 → boxd-0.1.3}/LICENSE +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/README.md +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/setup.cfg +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/__init__.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/__init__.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/api_pb2.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_generated/api_pb2_grpc.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_utils.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/_version_check.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/aio.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/auth.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/billing.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/boxes.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/client.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/disks.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/domains.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/errors.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/exec.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/networks.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/templates.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/tokens.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd/types.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/SOURCES.txt +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/dependency_links.txt +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/requires.txt +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/src/boxd.egg-info/top_level.txt +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_auth.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_boxes.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_exec.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_files.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_lifecycle.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_proxies.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_utils.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_v2.py +0 -0
- {boxd-0.1.2.dev13 → boxd-0.1.3}/tests/test_version_check.py +0 -0
|
@@ -88,6 +88,16 @@ class Box(_SyncBase):
|
|
|
88
88
|
"""Resume this VM from suspension."""
|
|
89
89
|
return self._run(self._async.resume())
|
|
90
90
|
|
|
91
|
+
def wait_until_ready(self, timeout: float = 90.0, poll_interval: float = 0.5) -> None:
|
|
92
|
+
"""Block until the VM is fully ready (status='running' + exec works).
|
|
93
|
+
|
|
94
|
+
Call this after ``create()`` or ``fork()`` before any operation that
|
|
95
|
+
depends on the VM being fully usable — especially before another
|
|
96
|
+
``fork()``, which gates fast-path live-fork on the source VM's Raft
|
|
97
|
+
status. See :meth:`boxd.box.Box.wait_until_ready` for details.
|
|
98
|
+
"""
|
|
99
|
+
self._run(self._async.wait_until_ready(timeout=timeout, poll_interval=poll_interval))
|
|
100
|
+
|
|
91
101
|
# ── Exec ─────────────────────────────────────────────────────
|
|
92
102
|
|
|
93
103
|
def exec(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import time
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import TYPE_CHECKING
|
|
@@ -15,6 +16,8 @@ from .errors import from_grpc_error
|
|
|
15
16
|
from .exec import ExecProcess, ExecResult, StreamReader, StreamWriter
|
|
16
17
|
from .types import Proxy, ResumeResult, SuspendResult
|
|
17
18
|
|
|
19
|
+
_TERMINAL_STATES = frozenset({"destroyed", "failed", "stopped"})
|
|
20
|
+
|
|
18
21
|
if TYPE_CHECKING:
|
|
19
22
|
from .client import Compute
|
|
20
23
|
|
|
@@ -74,6 +77,49 @@ class Box(GrpcCaller):
|
|
|
74
77
|
self.status = "running"
|
|
75
78
|
return ResumeResult(resume_us=resp.resume_us)
|
|
76
79
|
|
|
80
|
+
async def wait_until_ready(
|
|
81
|
+
self,
|
|
82
|
+
timeout: float = 90.0,
|
|
83
|
+
poll_interval: float = 0.5,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Block until the VM is fully ready to use.
|
|
86
|
+
|
|
87
|
+
Returns once the VM's status flips to ``running`` AND an ``exec``
|
|
88
|
+
round-trip succeeds.
|
|
89
|
+
|
|
90
|
+
``create()`` and ``fork()`` return as soon as the server has scheduled
|
|
91
|
+
the VM — at that point ``exec`` may already work, but the VM's status
|
|
92
|
+
in Raft can still be ``pending`` for ~1s. Operations that gate on the
|
|
93
|
+
authoritative state (notably another ``fork()``) will see the stale
|
|
94
|
+
status and fall back to a cold-boot path that drops the source's
|
|
95
|
+
in-memory state (e.g. tmpfs). Always call ``wait_until_ready()``
|
|
96
|
+
before acting on a fresh Box.
|
|
97
|
+
"""
|
|
98
|
+
deadline = time.monotonic() + timeout
|
|
99
|
+
|
|
100
|
+
while time.monotonic() < deadline:
|
|
101
|
+
fresh = await self._client.box.get(self.id)
|
|
102
|
+
self.status = fresh.status
|
|
103
|
+
if fresh.status == "running":
|
|
104
|
+
break
|
|
105
|
+
if fresh.status in _TERMINAL_STATES:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
f"VM {self.name} entered terminal state '{fresh.status}' while waiting for ready"
|
|
108
|
+
)
|
|
109
|
+
await asyncio.sleep(poll_interval)
|
|
110
|
+
else:
|
|
111
|
+
raise TimeoutError(f"VM {self.name} did not reach 'running' within {timeout}s")
|
|
112
|
+
|
|
113
|
+
while time.monotonic() < deadline:
|
|
114
|
+
try:
|
|
115
|
+
await self.exec("true")
|
|
116
|
+
return
|
|
117
|
+
except Exception:
|
|
118
|
+
await asyncio.sleep(poll_interval)
|
|
119
|
+
raise TimeoutError(
|
|
120
|
+
f"VM {self.name} reached 'running' but exec did not succeed within {timeout}s"
|
|
121
|
+
)
|
|
122
|
+
|
|
77
123
|
# ── Exec ──────────────────────────────────────────────────────
|
|
78
124
|
|
|
79
125
|
async def exec(
|
|
@@ -13,14 +13,10 @@ from boxd.errors import BoxdError
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _wait_ready(box, timeout=90):
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return i
|
|
21
|
-
except Exception:
|
|
22
|
-
time.sleep(1)
|
|
23
|
-
pytest.fail(f"VM {box.name} not ready after {timeout}s")
|
|
16
|
+
"""Wait until the VM is fully ready (status=running + exec works)."""
|
|
17
|
+
t0 = time.monotonic()
|
|
18
|
+
box.wait_until_ready(timeout=float(timeout))
|
|
19
|
+
return int(time.monotonic() - t0)
|
|
24
20
|
|
|
25
21
|
|
|
26
22
|
pytestmark = pytest.mark.e2e
|
|
@@ -8,13 +8,9 @@ pytestmark = pytest.mark.e2e
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _wait_ready(box, timeout=90):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return i
|
|
15
|
-
except Exception:
|
|
16
|
-
time.sleep(1)
|
|
17
|
-
pytest.fail(f"VM {box.name} not ready after {timeout}s")
|
|
11
|
+
t0 = time.monotonic()
|
|
12
|
+
box.wait_until_ready(timeout=float(timeout))
|
|
13
|
+
return int(time.monotonic() - t0)
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
# ── Domains ─────────────────────────────────────────────────────
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|