Skip to content

Commit 8bedd73

Browse files
authored
Prevent Hot Aisle min reservation period error (#3633)
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 bfe44d3 commit 8bedd73

File tree

2 files changed

+26
-2
lines changed

2 files changed

+26
-2
lines changed

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)