Skip to content

Commit c90cdf1

Browse files
authored
Display InstanceAvailability.NO_BALANCE in CLI (#3460)
In apply plans and `dstack offer`, display the `NO_BALANCE` availability as `no balance` rather than an empty string. Small related changes: - Refactor availability formatting so that it is consistent across run plans, fleet plans, and `dstack offer`. In fleet plans, availabilities are now displayed in lower case (previously, this was the only place where they were capitalized). - In `dstack offer --group-by gpu`, if a GPU is unavailable due to more than one reason, display all those reasons (previously, only one of the availabilities was displayed). - Default to dispalying unknown availabilities rather that falling back to an empty string. This will allow new availability types added in the future to automatically become visible in the CLI.
1 parent a36577f commit c90cdf1

6 files changed

Lines changed: 22 additions & 29 deletions

File tree

frontend/src/pages/Offers/List/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ export const OfferList: React.FC<OfferListProps> = ({ withSearchParams, onChange
181181
{
182182
id: 'availability',
183183
content: (gpu: IGpu) => {
184+
// FIXME: array to string comparison never passes.
185+
// Additionally, there are more availability statuses that are worth displaying,
186+
// and several of them may be present at once.
187+
184188
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
185189
// @ts-expect-error
186190
if (gpu.availability === 'not_available') {

src/dstack/_internal/cli/services/configurators/fleet.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
NO_OFFERS_WARNING,
1515
confirm_ask,
1616
console,
17+
format_instance_availability,
1718
)
1819
from dstack._internal.cli.utils.fleet import get_fleets_table
1920
from dstack._internal.cli.utils.rich import MultiItemStatus
@@ -32,7 +33,7 @@
3233
FleetSpec,
3334
InstanceGroupPlacement,
3435
)
35-
from dstack._internal.core.models.instances import InstanceAvailability, InstanceStatus, SSHKey
36+
from dstack._internal.core.models.instances import InstanceStatus, SSHKey
3637
from dstack._internal.core.services.diff import diff_models
3738
from dstack._internal.utils.common import local_time
3839
from dstack._internal.utils.logging import get_logger
@@ -420,12 +421,6 @@ def th(s: str) -> str:
420421
for index, offer in enumerate(print_offers, start=1):
421422
resources = offer.instance.resources
422423

423-
availability = ""
424-
if offer.availability in {
425-
InstanceAvailability.NOT_AVAILABLE,
426-
InstanceAvailability.NO_QUOTA,
427-
}:
428-
availability = offer.availability.value.replace("_", " ").title()
429424
offers_table.add_row(
430425
f"{index}",
431426
offer.backend.replace("remote", "ssh"),
@@ -434,7 +429,7 @@ def th(s: str) -> str:
434429
resources.pretty_format(),
435430
"yes" if resources.spot else "no",
436431
f"${offer.price:3f}".rstrip("0").rstrip("."),
437-
availability,
432+
format_instance_availability(offer.availability),
438433
style=None if index == 1 else "secondary",
439434
)
440435
if len(plan.offers) > offers_limit:

src/dstack/_internal/cli/utils/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from dstack._internal import settings
1313
from dstack._internal.cli.utils.rich import DstackRichHandler
1414
from dstack._internal.core.errors import CLIError, DstackError
15+
from dstack._internal.core.models.instances import InstanceAvailability
1516
from dstack._internal.utils.common import get_dstack_dir, parse_since
1617

1718
_colors = {
@@ -146,3 +147,9 @@ def resolve_url(url: str, timeout: float = 5.0) -> str:
146147
except requests.exceptions.ConnectionError as e:
147148
raise ValueError(f"Failed to resolve url {url}") from e
148149
return response.url
150+
151+
152+
def format_instance_availability(v: InstanceAvailability) -> str:
153+
if v in (InstanceAvailability.UNKNOWN, InstanceAvailability.AVAILABLE):
154+
return ""
155+
return v.value.replace("_", " ").lower()

src/dstack/_internal/cli/utils/gpu.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rich.table import Table
55

66
from dstack._internal.cli.models.offers import OfferCommandGroupByGpuOutput, OfferRequirements
7-
from dstack._internal.cli.utils.common import console
7+
from dstack._internal.cli.utils.common import console, format_instance_availability
88
from dstack._internal.core.models.gpus import GpuGroup
99
from dstack._internal.core.models.profiles import SpotPolicy
1010
from dstack._internal.core.models.runs import Requirements, RunSpec, get_policy_map
@@ -117,13 +117,10 @@ def print_gpu_table(gpus: List[GpuGroup], run_spec: RunSpec, group_by: List[str]
117117

118118
availability = ""
119119
has_available = any(av.is_available() for av in gpu_group.availability)
120-
has_unavailable = any(not av.is_available() for av in gpu_group.availability)
121-
122-
if has_unavailable and not has_available:
123-
for av in gpu_group.availability:
124-
if av.value in {"not_available", "no_quota", "idle", "busy"}:
125-
availability = av.value.replace("_", " ").lower()
126-
break
120+
if not has_available:
121+
availability = ", ".join(
122+
map(format_instance_availability, set(gpu_group.availability))
123+
)
127124

128125
secondary_style = "grey58"
129126
row_data = [

src/dstack/_internal/cli/utils/run.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
NO_OFFERS_WARNING,
1212
add_row_from_dict,
1313
console,
14+
format_instance_availability,
1415
)
1516
from dstack._internal.core.models.backends.base import BackendType
1617
from dstack._internal.core.models.configurations import DevEnvironmentConfiguration
1718
from dstack._internal.core.models.instances import (
18-
InstanceAvailability,
1919
InstanceOfferWithAvailability,
2020
InstanceType,
2121
)
@@ -168,14 +168,6 @@ def th(s: str) -> str:
168168
for i, offer in enumerate(job_plan.offers, start=1):
169169
r = offer.instance.resources
170170

171-
availability = ""
172-
if offer.availability in {
173-
InstanceAvailability.NOT_AVAILABLE,
174-
InstanceAvailability.NO_QUOTA,
175-
InstanceAvailability.IDLE,
176-
InstanceAvailability.BUSY,
177-
}:
178-
availability = offer.availability.value.replace("_", " ").lower()
179171
instance = offer.instance.name
180172
if offer.total_blocks > 1:
181173
instance += f" ({offer.blocks}/{offer.total_blocks})"
@@ -185,7 +177,7 @@ def th(s: str) -> str:
185177
r.pretty_format(include_spot=True),
186178
instance,
187179
f"${offer.price:.4f}".rstrip("0").rstrip("."),
188-
availability,
180+
format_instance_availability(offer.availability),
189181
style=None if i == 1 or not include_run_properties else "secondary",
190182
)
191183
if job_plan.total_offers > len(job_plan.offers):

src/dstack/_internal/core/models/instances.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,7 @@ class InstanceAvailability(Enum):
205205
AVAILABLE = "available"
206206
NOT_AVAILABLE = "not_available"
207207
NO_QUOTA = "no_quota"
208-
NO_BALANCE = (
209-
"no_balance" # Introduced in 0.19.24, may be used after a short compatibility period
210-
)
208+
NO_BALANCE = "no_balance" # For dstack Sky
211209
IDLE = "idle"
212210
BUSY = "busy"
213211

0 commit comments

Comments
 (0)