Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/source/getting-started/configuration/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ token: "******************" # An authentication token
drivers:
allow: ["jumpstarter_drivers_*", "vendorpackage.*"] # Driver packages the client can dynamically load
unsafe: false # Allow any driver package to load dynamically
leases:
acquisition_timeout: 7200 # Timeout in seconds for lease acquisition (default: 7200)
dial_timeout: 60 # Time limit for the exporter to become ready after lease is acquired (default: 60)
retry_timeout: 300 # Time limit for re-attempting a lease when the exporter becomes unreachable (default: 300, 0 to disable)
```

**Environment Variables**:
Expand All @@ -59,6 +63,16 @@ drivers:
- `JMP_TOKEN` - Auth token (overrides config file)
- `JMP_DRIVERS_ALLOW` - Comma-separated list of allowed driver namespaces
- `JUMPSTARTER_FORCE_SYSTEM_CERTS` - Set to `1` to force system CA certificates
- `JMP_RETRY_TIMEOUT` - Retry timeout in seconds for unreachable exporters (overrides config, default: 300)
- `JMP_DIAL_TIMEOUT` - Dial timeout in seconds for slow exporters (overrides config, default: 60)
- `JMP_OIDC_CALLBACK_PORT` - Local port for the OIDC callback during `jmp login` (useful for SSH tunneling; default: OS-assigned)
- `JMP_GRPC_PASSPHRASE` - Shared passphrase for authenticating against passphrase-protected exporters

**Shell Session Variables** (automatically set by `jmp shell`):

- `JMP_LEASE` - Active lease name (enables reconnection via `JMP_LEASE=<name> jmp shell`)
- `JMP_EXPORTER` - Name of the connected exporter
- `JMP_EXPORTER_LABELS` - Connected exporter's labels as comma-separated `key=value` pairs

**CLI Commands**:
```{code-block} console
Comment on lines 63 to 78

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The docs additions for JMP_OIDC_CALLBACK_PORT, JMP_GRPC_PASSPHRASE, shell session variables, and JMP_DISABLE_COMPRESSION are great to have documented but are unrelated to the dial reliability fix. Consider splitting into a separate commit/PR for cleaner scope. Not a blocker.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah just a misc doc addition, not worth a separate pr imo.. just shipped alongside, that's intentional

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry this was automated and I thought I had deleted it.

Expand Down Expand Up @@ -133,6 +147,7 @@ boundaries. See [{term}`Hook`s](../../introduction/hooks.md) for full details on
- `JMP_TOKEN` - Auth token (overrides config file)
- `JMP_NAMESPACE` - Namespace in the {term}`controller`
- `JMP_NAME` - {term}`Exporter` name
- `JMP_DISABLE_COMPRESSION` - Set to `1` to disable stream compression (gzip, xz, bz2, zstd) for driver data transfers

**CLI Commands**:
```{code-block} console
Expand Down
6 changes: 3 additions & 3 deletions docs/source/getting-started/configuration/loading-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ precedence (highest to lowest):

For client operations, Jumpstarter processes configurations in this order:

1. **Command-line options** such as `--endpoint` or `--client-config`
2. **Environment variables** such as `JMP_ENDPOINT`, `JMP_TOKEN`, or
`JMP_CLIENT_CONFIG`
1. **Command-line options** such as `--endpoint`, `--client-config`, `--retry-timeout`, or `--dial-timeout`
2. **Environment variables** such as `JMP_ENDPOINT`, `JMP_TOKEN`,
`JMP_CLIENT_CONFIG`, `JMP_RETRY_TIMEOUT`, or `JMP_DIAL_TIMEOUT`
3. **Current client** defined in `${HOME}/.config/jumpstarter/config.yaml`
4. **Specific client file** in `${HOME}/.config/jumpstarter/clients/<n>.yaml`

Expand Down
14 changes: 14 additions & 0 deletions python/packages/jumpstarter-cli/jumpstarter_cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,20 @@ def convert(self, value, param, ctx):
),
)

DIAL_TIMEOUT = DurationParamType(minimum=timedelta(seconds=5))

opt_dial_timeout = partial(
click.option,
"--dial-timeout",
"dial_timeout",
type=DIAL_TIMEOUT,
default=None,
help=(
"Override dial timeout for slow exporters (e.g., '60s', '2m', "
"'90s'). Env: JMP_DIAL_TIMEOUT. Default: 60s."
),
)

opt_begin_time = click.option(
"--begin-time",
"begin_time",
Expand Down
21 changes: 18 additions & 3 deletions python/packages/jumpstarter-cli/jumpstarter_cli/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@
)
from jumpstarter_cli_common.signal import signal_handler

from .common import opt_acquisition_timeout, opt_duration_partial, opt_exporter_name, opt_retry_timeout, opt_selector
from .common import (
opt_acquisition_timeout,
opt_dial_timeout,
opt_duration_partial,
opt_exporter_name,
opt_retry_timeout,
opt_selector,
)
from .login import relogin_client
from jumpstarter.client import DirectLease
from jumpstarter.client.client import client_from_path, fetch_motd
Expand Down Expand Up @@ -473,7 +480,7 @@ async def _run_shell_with_lease_async(lease, exporter_logs, config, command, can

async def _shell_with_signal_handling( # noqa: C901
config, selector, exporter_name, lease_name, duration, exporter_logs, command, acquisition_timeout,
retry_timeout=None,
retry_timeout=None, dial_timeout=None,
):
"""Handle lease acquisition and shell execution with signal handling."""
exit_code = 0
Expand All @@ -500,7 +507,7 @@ async def _shell_with_signal_handling( # noqa: C901
while True:
async with config.lease_async(
selector, exporter_name, lease_name, duration, portal, acquisition_timeout,
retry_timeout=retry_timeout,
retry_timeout=retry_timeout, dial_timeout=dial_timeout,
) as lease:
lease_used = lease

Expand All @@ -521,6 +528,11 @@ async def _shell_with_signal_handling( # noqa: C901
if unreachable is not None:
if lease.lease_ended:
break # lease expired naturally — exit cleanly
if lease.lease_transferred:
raise ExporterOfflineError(
"Lease has been transferred to another client. "
"Session is no longer valid."
) from unreachable
if connect_deadline is None:
connect_deadline = time.monotonic() + lease.retry_timeout
if time.monotonic() >= connect_deadline:
Expand Down Expand Up @@ -689,6 +701,7 @@ async def _shell_direct_async(
@click.option("--exporter-logs", is_flag=True, help="Enable exporter log streaming")
@opt_acquisition_timeout()
@opt_retry_timeout()
@opt_dial_timeout()
# direct connection (no controller)
@click.option(
"--tls-grpc",
Expand Down Expand Up @@ -720,6 +733,7 @@ def shell(
exporter_logs,
acquisition_timeout,
retry_timeout,
dial_timeout,
tls_grpc_address,
tls_grpc_insecure,
passphrase,
Expand Down Expand Up @@ -769,6 +783,7 @@ def shell(
command,
acquisition_timeout,
retry_timeout,
dial_timeout,
)
sys.exit(exit_code)

Expand Down
39 changes: 23 additions & 16 deletions python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __init__(self):
@asynccontextmanager
async def lease_async(
self, selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
self.captured = (selector, exporter_name, lease_name, duration, acquisition_timeout)
m = Mock()
Expand Down Expand Up @@ -113,7 +113,7 @@ async def test_shell_warns_when_expired_token_prevents_cleanup_on_normal_exit():
@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

Expand Down Expand Up @@ -162,6 +162,7 @@ def test_shell_requires_selector_or_name_when_no_leases():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand All @@ -183,6 +184,7 @@ def test_shell_allows_existing_lease_name_without_selector_or_name():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand All @@ -208,6 +210,7 @@ def test_shell_auto_connects_single_lease():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand Down Expand Up @@ -236,6 +239,7 @@ def test_shell_no_leases_shows_guidance():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand Down Expand Up @@ -277,6 +281,7 @@ def test_shell_multi_lease_no_tty_error():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand Down Expand Up @@ -313,6 +318,7 @@ def test_shell_no_own_leases_among_others():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand All @@ -335,6 +341,7 @@ def test_shell_allows_env_lease_without_selector_or_name():
exporter_logs=False,
acquisition_timeout=None,
retry_timeout=None,
dial_timeout=None,
tls_grpc_address=None,
tls_grpc_insecure=False,
passphrase=None,
Expand Down Expand Up @@ -1053,9 +1060,9 @@ def _make_config_with_lease(self, lease):

@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
):
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

config.lease_async = lease_async
Expand Down Expand Up @@ -1136,9 +1143,9 @@ async def test_retries_then_raises_on_timeout(self):

@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
):
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

config.lease_async = lease_async
Expand Down Expand Up @@ -1180,9 +1187,9 @@ async def test_retries_when_wrapped_in_exception_group(self):

@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
):
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

config.lease_async = lease_async
Expand Down Expand Up @@ -1222,9 +1229,9 @@ async def test_retry_succeeds_before_timeout(self):

@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
):
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

config.lease_async = lease_async
Expand Down Expand Up @@ -1266,7 +1273,7 @@ async def test_exits_cleanly_when_lease_ended_during_connection(self):
@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

Expand Down Expand Up @@ -1303,7 +1310,7 @@ async def test_retries_normally_when_lease_not_ended(self):
@asynccontextmanager
async def lease_async(
selector, exporter_name, lease_name, duration, portal,
acquisition_timeout, retry_timeout=None,
acquisition_timeout, retry_timeout=None, dial_timeout=None,
):
yield lease

Expand Down
Loading
Loading