Skip to content

Commit 5c81edd

Browse files
committed
Prevent Hot Aisle min reservation period error
Force delete every Hot Aisle instance to prevent the deletion error if the minimum reservation period is not met and prevent orphaned instances.
1 parent 4b4d1f6 commit 5c81edd

2 files changed

Lines changed: 26 additions & 2 deletions

File tree

docs/docs/concepts/backends.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,18 @@ projects:
891891
* **Owner role for the user** - Required for creating and managing SSH keys
892892
* **Operator role for the team** - Required for managing virtual machines within the team
893893

894+
??? info "Pricing"
895+
`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.
896+
897+
See the Hot Aisle API for the minimum reservation period for each instance type:
898+
899+
<div class="termy">
900+
901+
```shell
902+
$ curl -H "Authorization: Token $API_KEY" https://admin.hotaisle.app/api/teams/$TEAM_HANDLE/virtual_machines/available/ | jq ".[] | {gpus: .Specs.gpus, MinimumReservationMinutes}"
903+
```
904+
905+
</div>
894906

895907
### CloudRift
896908

src/dstack/_internal/core/backends/hotaisle/api_client.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,25 @@ def get_vm_state(self, vm_name: str) -> str:
7676

7777
def terminate_virtual_machine(self, vm_name: str) -> None:
7878
url = f"{API_URL}/teams/{self.team_handle}/virtual_machines/{vm_name}/"
79-
response = self._make_request("DELETE", url)
79+
response = self._make_request(
80+
"DELETE",
81+
url,
82+
params={
83+
"force": "true", # delete even if min reservation time not met
84+
},
85+
)
8086
if response.status_code == 404:
8187
logger.debug("Hot Aisle virtual machine %s not found", vm_name)
8288
return
8389
response.raise_for_status()
8490

8591
def _make_request(
86-
self, method: str, url: str, json: Optional[Dict[str, Any]] = None, timeout: int = 30
92+
self,
93+
method: str,
94+
url: str,
95+
json: Optional[dict[str, Any]] = None,
96+
params: Optional[dict[str, str]] = None,
97+
timeout: int = 30,
8798
) -> requests.Response:
8899
headers = {
89100
"accept": "application/json",
@@ -97,5 +108,6 @@ def _make_request(
97108
url=url,
98109
headers=headers,
99110
json=json,
111+
params=params,
100112
timeout=timeout,
101113
)

0 commit comments

Comments
 (0)