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
12 changes: 12 additions & 0 deletions docs/docs/concepts/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,18 @@ projects:
* **Owner role for the user** - Required for creating and managing SSH keys
* **Operator role for the team** - Required for managing virtual machines within the team

??? info "Pricing"
`dstack` shows the hourly price for Hot Aisle instances. Some instances also require an upfront payment for a minimum reservation period, which is usually a few hours. You will be charged for the full minimum period even if you stop the instance early.

See the Hot Aisle API for the minimum reservation period for each instance type:

<div class="termy">

```shell
$ curl -H "Authorization: Token $API_KEY" https://admin.hotaisle.app/api/teams/$TEAM_HANDLE/virtual_machines/available/ | jq ".[] | {gpus: .Specs.gpus, MinimumReservationMinutes}"
```

</div>

### CloudRift

Expand Down
16 changes: 14 additions & 2 deletions src/dstack/_internal/core/backends/hotaisle/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,25 @@ def get_vm_state(self, vm_name: str) -> str:

def terminate_virtual_machine(self, vm_name: str) -> None:
url = f"{API_URL}/teams/{self.team_handle}/virtual_machines/{vm_name}/"
response = self._make_request("DELETE", url)
response = self._make_request(
"DELETE",
url,
params={
"force": "true", # delete even if min reservation time not met
},
)
if response.status_code == 404:
logger.debug("Hot Aisle virtual machine %s not found", vm_name)
return
response.raise_for_status()

def _make_request(
self, method: str, url: str, json: Optional[Dict[str, Any]] = None, timeout: int = 30
self,
method: str,
url: str,
json: Optional[dict[str, Any]] = None,
params: Optional[dict[str, str]] = None,
timeout: int = 30,
) -> requests.Response:
headers = {
"accept": "application/json",
Expand All @@ -97,5 +108,6 @@ def _make_request(
url=url,
headers=headers,
json=json,
params=params,
timeout=timeout,
)