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
3 changes: 1 addition & 2 deletions src/dstack/_internal/core/backends/aws/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
if quota is not None and not quota:
availability = InstanceAvailability.NO_QUOTA
availability_offers.append(
InstanceOfferWithAvailability(
**offer.dict(),
offer.with_availability(
availability=availability,
availability_zones=regions_to_zones[offer.region],
)
Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/azure/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,7 @@ def get_location_quotas(location: str) -> List[str]:
availability = InstanceAvailability.NO_QUOTA
if (offer.instance.name, offer.region) in has_quota:
availability = InstanceAvailability.UNKNOWN
offers_with_availability.append(
InstanceOfferWithAvailability(**offer.dict(), availability=availability)
)
offers_with_availability.append(offer.with_availability(availability=availability))

return offers_with_availability

Expand Down
11 changes: 6 additions & 5 deletions src/dstack/_internal/core/backends/base/offers.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def catalog_item_to_offer(
requirements: Optional[Requirements],
configurable_disk_size: Range[Memory],
) -> Optional[InstanceOffer]:
# Gpu() keeps validation for vendor normalization.
# The rest use construct() to skip redundant validation — data comes from already validated CatalogItem.
gpus = []
if item.gpu_count > 0:
gpu = Gpu(
Expand All @@ -93,17 +95,17 @@ def catalog_item_to_offer(
)
if disk_size_mib is None:
return None
resources = Resources(
resources = Resources.construct(
cpu_arch=item.cpu_arch,
cpus=item.cpu,
memory_mib=round(item.memory * 1024),
gpus=gpus,
spot=item.spot,
disk=Disk(size_mib=disk_size_mib),
disk=Disk.construct(size_mib=disk_size_mib),
)
return InstanceOffer(
return InstanceOffer.construct(
backend=backend,
instance=InstanceType(
instance=InstanceType.construct(
name=item.instance_name,
resources=resources,
),
Expand Down Expand Up @@ -236,7 +238,6 @@ def modifier(offer: InstanceOfferWithAvailability) -> Optional[InstanceOfferWith
offer_copy.instance.resources.disk = Disk(
size_mib=get_or_error(disk_size_range.min) * 1024
)
offer_copy.instance.resources.update_description()
return offer_copy

return modifier
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/cloudrift/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,7 @@ def _get_offers_with_availability(
for offer in offers:
key = (offer.instance.name, offer.region)
availability = region_availabilities.get(key, InstanceAvailability.NOT_AVAILABLE)
availability_offers.append(
InstanceOfferWithAvailability(**offer.dict(), availability=availability)
)
availability_offers.append(offer.with_availability(availability=availability))

return availability_offers

Expand Down
7 changes: 1 addition & 6 deletions src/dstack/_internal/core/backends/crusoe/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
else InstanceAvailability.NO_QUOTA
)
break
result.append(
InstanceOfferWithAvailability(
**offer.dict(),
availability=availability,
)
)
result.append(offer.with_availability(availability=availability))
return result

def _get_quota_map(self) -> dict[str, int]:
Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/cudo/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ def get_offers_by_requirements(
requirements=requirements,
)
offers = [
InstanceOfferWithAvailability(
**offer.dict(), availability=InstanceAvailability.AVAILABLE
)
offer.with_availability(availability=InstanceAvailability.AVAILABLE)
for offer in offers
# in-hyderabad-1 is known to have provisioning issues
if offer.region not in ["in-hyderabad-1"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
catalog=self.catalog,
)
return [
InstanceOfferWithAvailability(
**offer.dict(),
availability=InstanceAvailability.AVAILABLE,
)
offer.with_availability(availability=InstanceAvailability.AVAILABLE)
for offer in offers
]

Expand Down
3 changes: 1 addition & 2 deletions src/dstack/_internal/core/backends/gcp/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
if _has_gpu_quota(quotas[region], offer.instance.resources):
availability = InstanceAvailability.UNKNOWN
# todo quotas: cpu, memory, global gpu, tpu
offer_with_availability = InstanceOfferWithAvailability(
**offer.dict(),
offer_with_availability = offer.with_availability(
availability=availability,
availability_zones=zones_by_key.get(key, []),
)
Expand Down
5 changes: 1 addition & 4 deletions src/dstack/_internal/core/backends/hotaisle/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
extra_filter=_supported_instances,
)
return [
InstanceOfferWithAvailability(
**offer.dict(),
availability=InstanceAvailability.AVAILABLE,
)
offer.with_availability(availability=InstanceAvailability.AVAILABLE)
for offer in offers
]

Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/lambdalabs/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,7 @@ def _get_offers_with_availability(
availability = InstanceAvailability.NOT_AVAILABLE
if offer.region in instance_availability.get(offer.instance.name, []):
availability = InstanceAvailability.AVAILABLE
availability_offers.append(
InstanceOfferWithAvailability(**offer.dict(), availability=availability)
)
availability_offers.append(offer.with_availability(availability=availability))
return availability_offers


Expand Down
6 changes: 1 addition & 5 deletions src/dstack/_internal/core/backends/nebius/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
extra_filter=_supported_instances,
)
return [
InstanceOfferWithAvailability(
**offer.dict(),
availability=InstanceAvailability.UNKNOWN,
)
for offer in offers
offer.with_availability(availability=InstanceAvailability.UNKNOWN) for offer in offers
]

def get_offers_modifiers(self, requirements: Requirements) -> Iterable[OfferModifier]:
Expand Down
3 changes: 1 addition & 2 deletions src/dstack/_internal/core/backends/oci/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
else:
availability = InstanceAvailability.NO_QUOTA
offers_with_availability.append(
InstanceOfferWithAvailability(
**offer.dict(),
offer.with_availability(
availability=availability,
availability_zones=shapes_availability[offer.region].get(
offer.instance.name, []
Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/runpod/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
extra_filter=lambda o: _is_secure_cloud(o.region) or self.config.allow_community_cloud,
)
offers = [
InstanceOfferWithAvailability(
**offer.dict(), availability=InstanceAvailability.AVAILABLE
)
offer.with_availability(availability=InstanceAvailability.AVAILABLE)
for offer in offers
]
return offers
Expand Down
5 changes: 1 addition & 4 deletions src/dstack/_internal/core/backends/template/compute.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,7 @@ class {{ backend_name }}Compute(
)
# TODO: Add availability info to offers
return (
InstanceOfferWithAvailability(
**offer.dict(),
availability=InstanceAvailability.UNKNOWN,
)
offer.with_availability(availability=InstanceAvailability.UNKNOWN)
for offer in offers
)

Expand Down
3 changes: 1 addition & 2 deletions src/dstack/_internal/core/backends/vastai/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ def get_offers_by_requirements(
catalog=self.catalog,
)
offers = [
InstanceOfferWithAvailability(
**offer.dict(),
offer.with_availability(
availability=InstanceAvailability.AVAILABLE,
instance_runtime=InstanceRuntime.RUNNER,
)
Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/verda/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,7 @@ def _get_offers_with_availability(
for offer in offers:
key = (offer.instance.name, offer.region)
availability = region_availabilities.get(key, InstanceAvailability.NOT_AVAILABLE)
availability_offers.append(
InstanceOfferWithAvailability(**offer.dict(), availability=availability)
)
availability_offers.append(offer.with_availability(availability=availability))

return availability_offers

Expand Down
4 changes: 1 addition & 3 deletions src/dstack/_internal/core/backends/vultr/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ def get_all_offers_with_availability(self) -> List[InstanceOfferWithAvailability
extra_filter=_supported_instances,
)
offers = [
InstanceOfferWithAvailability(
**offer.dict(), availability=InstanceAvailability.AVAILABLE
)
offer.with_availability(availability=InstanceAvailability.AVAILABLE)
for offer in offers
]
return offers
Expand Down
45 changes: 6 additions & 39 deletions src/dstack/_internal/core/models/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,6 @@ class Resources(CoreModel):
str,
Field(description="Deprecated: generated client-side. Will be removed in 0.21."),
] = ""
"""`description` is deprecated because it is now generated client-side."""

@root_validator
def _description(cls, values) -> Dict:
try:
description = values["description"]
if not description:
cpus = values["cpus"]
memory_mib = values["memory_mib"]
gpus = values["gpus"]
disk_size_mib = values["disk"].size_mib
spot = values["spot"]
cpu_arch = values["cpu_arch"]
values["description"] = Resources._pretty_format(
cpus=cpus,
cpu_arch=cpu_arch,
memory_mib=memory_mib,
disk_size_mib=disk_size_mib,
gpus=gpus,
spot=spot,
include_spot=True,
)
except KeyError:
return values
return values

def pretty_format(self, include_spot: bool = False, gpu_only: bool = False) -> str:
return Resources._pretty_format(
Expand All @@ -101,20 +76,6 @@ def pretty_format(self, include_spot: bool = False, gpu_only: bool = False) -> s
gpu_only,
)

def update_description(self):
"""
Call to update `description` after patching other properties.
"""
self.description = Resources._pretty_format(
cpus=self.cpus,
cpu_arch=self.cpu_arch,
memory_mib=self.memory_mib,
disk_size_mib=self.disk.size_mib,
gpus=self.gpus,
spot=self.spot,
include_spot=True,
)

@staticmethod
def _pretty_format(
cpus: int,
Expand Down Expand Up @@ -232,6 +193,12 @@ class InstanceOffer(CoreModel):
price: float
backend_data: dict[str, Any] = {}

def with_availability(self, **kwargs) -> "InstanceOfferWithAvailability":
"""Convert to InstanceOfferWithAvailability without re-serializing/re-validating fields.
The result shares nested objects with self. This is generally safe because callers
discard the original InstanceOffer after conversion."""
return InstanceOfferWithAvailability.construct(**self.__dict__, **kwargs)


class InstanceOfferWithAvailability(InstanceOffer):
availability: InstanceAvailability
Expand Down
6 changes: 3 additions & 3 deletions src/tests/_internal/server/routers/test_fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
"gpus": [],
"spot": False,
"disk": {"size_mib": 102400},
"description": "cpu=2 mem=0GB disk=100GB",
"description": "",
},
},
"name": f"{spec.configuration.name}-0",
Expand Down Expand Up @@ -1327,7 +1327,7 @@ async def test_updates_ssh_fleet(self, test_db, session: AsyncSession, client: A
"gpus": [],
"spot": False,
"disk": {"size_mib": 102400},
"description": "cpu=2 mem=0GB disk=100GB",
"description": "",
},
},
"name": "test-ssh-fleet-0",
Expand Down Expand Up @@ -1362,7 +1362,7 @@ async def test_updates_ssh_fleet(self, test_db, session: AsyncSession, client: A
"gpus": [],
"spot": False,
"disk": {"size_mib": 102400},
"description": "cpu=2 mem=0GB disk=100GB",
"description": "",
},
},
"name": "test-ssh-fleet-1",
Expand Down
Loading