request-vm-on-golem 0.1.50__tar.gz → 0.1.52__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/PKG-INFO +72 -34
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/README.md +71 -33
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/pyproject.toml +1 -1
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/api/main.py +9 -1
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/cli/commands.py +54 -12
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/config.py +129 -16
- request_vm_on_golem-0.1.52/requestor/payments/monitor.py +126 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/security/faucet.py +5 -2
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/provider_service.py +16 -3
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/vm_service.py +2 -2
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/cli/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/data/deployments/l2.json +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/db/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/db/sqlite.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/errors.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/payments/blockchain_service.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/provider/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/provider/client.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/run.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/database_service.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/ssh_service.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/ssh/__init__.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/ssh/manager.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/utils/logging.py +0 -0
- {request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/utils/spinner.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: request-vm-on-golem
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.52
|
4
4
|
Summary: VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network
|
5
5
|
Keywords: golem,vm,cloud,decentralized,cli
|
6
6
|
Author: Phillip Jensen
|
@@ -41,7 +41,40 @@ Description-Content-Type: text/markdown
|
|
41
41
|
|
42
42
|
# VM on Golem Requestor
|
43
43
|
|
44
|
-
|
44
|
+
Rent compute on demand — like Airbnb for servers. The `golem` CLI helps you discover providers, fund pay‑as‑you‑go streams, launch VMs, and connect via SSH.
|
45
|
+
|
46
|
+
## Quick Start (Rent a VM)
|
47
|
+
|
48
|
+
1) Install:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
pip install request-vm-on-golem
|
52
|
+
```
|
53
|
+
|
54
|
+
2) Find providers (testnet by default):
|
55
|
+
|
56
|
+
```bash
|
57
|
+
golem vm providers
|
58
|
+
```
|
59
|
+
|
60
|
+
3) Create a VM (auto‑opens a payment stream if needed):
|
61
|
+
|
62
|
+
```bash
|
63
|
+
golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20
|
64
|
+
```
|
65
|
+
|
66
|
+
4) SSH in:
|
67
|
+
|
68
|
+
```bash
|
69
|
+
golem vm ssh my-vm
|
70
|
+
```
|
71
|
+
|
72
|
+
5) Stop or destroy when done:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
golem vm stop my-vm
|
76
|
+
golem vm destroy my-vm
|
77
|
+
```
|
45
78
|
|
46
79
|
## Architecture Overview
|
47
80
|
|
@@ -141,7 +174,7 @@ CLI helpers
|
|
141
174
|
- Open a stream for a planned VM (computes rate from provider pricing):
|
142
175
|
|
143
176
|
```bash
|
144
|
-
|
177
|
+
golem vm stream open \
|
145
178
|
--provider-id 0xProvider \
|
146
179
|
--cpu 2 --memory 4 --storage 20 \
|
147
180
|
--hours 1
|
@@ -152,29 +185,39 @@ poetry run golem vm stream open \
|
|
152
185
|
|
153
186
|
```bash
|
154
187
|
# Add 3 hours at prior rate
|
155
|
-
|
188
|
+
golem vm stream topup --stream-id 123 --hours 3
|
156
189
|
|
157
190
|
# Or specify exact GLM amount
|
158
|
-
|
191
|
+
golem vm stream topup --stream-id 123 --glm 25.0
|
159
192
|
```
|
160
193
|
|
161
194
|
- Check stream status via provider (by VM name recorded in your DB):
|
162
195
|
|
163
196
|
```bash
|
164
|
-
|
197
|
+
golem vm stream status my-vm
|
165
198
|
# add --json for machine-readable output
|
166
199
|
```
|
167
200
|
|
168
201
|
- Inspect a stream directly on-chain:
|
169
202
|
|
170
203
|
```bash
|
171
|
-
|
204
|
+
golem vm stream inspect --stream-id 123
|
205
|
+
```
|
206
|
+
|
207
|
+
- Stopping or destroying a VM ends the stream:
|
208
|
+
|
209
|
+
```bash
|
210
|
+
# Stop VM and terminate payment stream (best-effort)
|
211
|
+
golem vm stop my-vm
|
212
|
+
|
213
|
+
# Destroy VM and terminate stream
|
214
|
+
golem vm destroy my-vm
|
172
215
|
```
|
173
216
|
|
174
217
|
- Create a VM and attach an existing stream (no auto-streams are created by the requestor):
|
175
218
|
|
176
219
|
```bash
|
177
|
-
|
220
|
+
golem vm create my-vm \
|
178
221
|
--provider-id 0xProvider \
|
179
222
|
--cpu 2 --memory 4 --storage 20 \
|
180
223
|
--stream-id 123
|
@@ -182,7 +225,8 @@ poetry run golem vm create my-vm \
|
|
182
225
|
|
183
226
|
Environment (env prefix `GOLEM_REQUESTOR_`):
|
184
227
|
|
185
|
-
- `
|
228
|
+
- `payments_network` — Payments network profile (defaults to `l2.holesky`). Profiles provide RPC + faucet defaults.
|
229
|
+
- `polygon_rpc_url` — EVM RPC URL (defaults from `payments_network` profile; can be overridden)
|
186
230
|
- `stream_payment_address` — StreamPayment address (defaults from `contracts/deployments/l2.json`; overridden by provider info)
|
187
231
|
- `glm_token_address` — Token address (defaults from `contracts/deployments/l2.json`; zero address means native ETH)
|
188
232
|
- Optional override of deployments directory: set `GOLEM_DEPLOYMENTS_DIR` to a folder containing `l2.json`.
|
@@ -195,16 +239,21 @@ Efficiency tips:
|
|
195
239
|
- Withdrawals are typically executed by providers; requestors don’t need to withdraw.
|
196
240
|
- The CLI `vm stream open` will prefer the provider’s advertised contract/token addresses to prevent mismatches.
|
197
241
|
|
198
|
-
|
242
|
+
Monitoring and auto top-up:
|
243
|
+
|
244
|
+
- The requestor API runs a background monitor that keeps each running VM’s stream funded with at least 1 hour runway (configurable). It checks every 30s and tops up to the target runway.
|
245
|
+
- Configure via env (prefix `GOLEM_REQUESTOR_`): `stream_monitor_enabled` (default true), `stream_monitor_interval_seconds` (default 30), `stream_min_remaining_seconds` (default 3600), `stream_topup_target_seconds` (default 3600).
|
246
|
+
|
247
|
+
## Faucet (Testnet only)
|
199
248
|
|
200
249
|
- Request L2 test ETH to cover stream transactions:
|
201
250
|
|
202
251
|
```bash
|
203
|
-
|
252
|
+
golem wallet faucet
|
204
253
|
```
|
205
254
|
|
206
255
|
- Defaults:
|
207
|
-
- Faucet
|
256
|
+
- Faucet URL and enablement come from the active `payments_network` profile. On `mainnet` (or other profiles without faucet) the command is disabled.
|
208
257
|
- CAPTCHA: `https://cap.gobas.me/05381a2cef5e`
|
209
258
|
- Override with env: `GOLEM_REQUESTOR_l2_faucet_url`, `GOLEM_REQUESTOR_captcha_url`, `GOLEM_REQUESTOR_captcha_api_key`.
|
210
259
|
|
@@ -212,7 +261,7 @@ poetry run golem wallet faucet
|
|
212
261
|
|
213
262
|
```bash
|
214
263
|
# Install using pip
|
215
|
-
pip install
|
264
|
+
pip install request-vm-on-golem
|
216
265
|
|
217
266
|
# Or install from source
|
218
267
|
git clone https://github.com/golem/vm-on-golem.git
|
@@ -234,11 +283,7 @@ First, source the development environment variables:
|
|
234
283
|
source .env.dev
|
235
284
|
```
|
236
285
|
|
237
|
-
Then, run any `golem` command. For example:
|
238
|
-
|
239
|
-
```bash
|
240
|
-
poetry run golem vm providers
|
241
|
-
```
|
286
|
+
Then, run any `golem` command. For example: `golem vm providers`
|
242
287
|
|
243
288
|
### Prepending variables
|
244
289
|
|
@@ -255,19 +300,15 @@ GOLEM_REQUESTOR_ENVIRONMENT="development" GOLEM_REQUESTOR_FORCE_LOCALHOST="true"
|
|
255
300
|
- Does not determine chain selection.
|
256
301
|
|
257
302
|
- Network Selection (`--network` or `GOLEM_REQUESTOR_NETWORK`)
|
258
|
-
- Filters
|
259
|
-
|
260
|
-
|
303
|
+
- Filters results by `testnet|mainnet`. Defaults are sensible; most users don’t need to change anything.
|
304
|
+
|
305
|
+
- Payments Network (`GOLEM_REQUESTOR_PAYMENTS_NETWORK`)
|
306
|
+
- Selects the payments chain profile (e.g., `l2.holesky`, `mainnet`) used for streaming payments; sets default RPC and faucet behavior.
|
307
|
+
- Provider discovery filters by this payments network via `vm providers` unless `--all-payments` is supplied. Override payments filter with `--payments-network <name>`.
|
261
308
|
|
262
309
|
Examples:
|
263
|
-
- List providers on mainnet without changing env:
|
264
|
-
|
265
|
-
poetry run golem vm providers --network mainnet
|
266
|
-
```
|
267
|
-
- Create a VM while targeting testnet:
|
268
|
-
```bash
|
269
|
-
poetry run golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20 --network testnet
|
270
|
-
```
|
310
|
+
- List providers on mainnet without changing env: `golem vm providers --network mainnet`
|
311
|
+
- Create a VM while targeting testnet: `golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20 --network testnet`
|
271
312
|
|
272
313
|
## Usage
|
273
314
|
|
@@ -373,9 +414,6 @@ The requestor uses a hierarchical configuration system:
|
|
373
414
|
1. Environment Variables:
|
374
415
|
|
375
416
|
```bash
|
376
|
-
# Discovery Service
|
377
|
-
export GOLEM_REQUESTOR_DISCOVERY_URL="http://discovery.golem.network:9001"
|
378
|
-
|
379
417
|
# Base Directory (default: ~/.golem)
|
380
418
|
export GOLEM_REQUESTOR_BASE_DIR="/path/to/golem/dir"
|
381
419
|
|
@@ -386,7 +424,7 @@ export GOLEM_REQUESTOR_DB_PATH="/path/to/database.db"
|
|
386
424
|
# Environment Mode (defaults to "production")
|
387
425
|
export GOLEM_REQUESTOR_ENVIRONMENT="development" # Optional: Switch to development mode
|
388
426
|
export GOLEM_REQUESTOR_FORCE_LOCALHOST="true" # Optional: Force localhost in development mode
|
389
|
-
export GOLEM_REQUESTOR_NETWORK="testnet" # Or "mainnet";
|
427
|
+
export GOLEM_REQUESTOR_NETWORK="testnet" # Or "mainnet"; optional filter for listing/creation
|
390
428
|
```
|
391
429
|
|
392
430
|
2. Directory Structure:
|
@@ -423,7 +461,7 @@ Local state is maintained in SQLite:
|
|
423
461
|
|
424
462
|
The requestor communicates with providers through:
|
425
463
|
|
426
|
-
1.
|
464
|
+
1. Network discovery (uses sane defaults; no setup required for most users)
|
427
465
|
2. Direct API calls for VM management
|
428
466
|
3. SSH proxy system for secure access
|
429
467
|
4. Resource tracking for capacity management
|
@@ -1,6 +1,39 @@
|
|
1
1
|
# VM on Golem Requestor
|
2
2
|
|
3
|
-
|
3
|
+
Rent compute on demand — like Airbnb for servers. The `golem` CLI helps you discover providers, fund pay‑as‑you‑go streams, launch VMs, and connect via SSH.
|
4
|
+
|
5
|
+
## Quick Start (Rent a VM)
|
6
|
+
|
7
|
+
1) Install:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
pip install request-vm-on-golem
|
11
|
+
```
|
12
|
+
|
13
|
+
2) Find providers (testnet by default):
|
14
|
+
|
15
|
+
```bash
|
16
|
+
golem vm providers
|
17
|
+
```
|
18
|
+
|
19
|
+
3) Create a VM (auto‑opens a payment stream if needed):
|
20
|
+
|
21
|
+
```bash
|
22
|
+
golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20
|
23
|
+
```
|
24
|
+
|
25
|
+
4) SSH in:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
golem vm ssh my-vm
|
29
|
+
```
|
30
|
+
|
31
|
+
5) Stop or destroy when done:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
golem vm stop my-vm
|
35
|
+
golem vm destroy my-vm
|
36
|
+
```
|
4
37
|
|
5
38
|
## Architecture Overview
|
6
39
|
|
@@ -100,7 +133,7 @@ CLI helpers
|
|
100
133
|
- Open a stream for a planned VM (computes rate from provider pricing):
|
101
134
|
|
102
135
|
```bash
|
103
|
-
|
136
|
+
golem vm stream open \
|
104
137
|
--provider-id 0xProvider \
|
105
138
|
--cpu 2 --memory 4 --storage 20 \
|
106
139
|
--hours 1
|
@@ -111,29 +144,39 @@ poetry run golem vm stream open \
|
|
111
144
|
|
112
145
|
```bash
|
113
146
|
# Add 3 hours at prior rate
|
114
|
-
|
147
|
+
golem vm stream topup --stream-id 123 --hours 3
|
115
148
|
|
116
149
|
# Or specify exact GLM amount
|
117
|
-
|
150
|
+
golem vm stream topup --stream-id 123 --glm 25.0
|
118
151
|
```
|
119
152
|
|
120
153
|
- Check stream status via provider (by VM name recorded in your DB):
|
121
154
|
|
122
155
|
```bash
|
123
|
-
|
156
|
+
golem vm stream status my-vm
|
124
157
|
# add --json for machine-readable output
|
125
158
|
```
|
126
159
|
|
127
160
|
- Inspect a stream directly on-chain:
|
128
161
|
|
129
162
|
```bash
|
130
|
-
|
163
|
+
golem vm stream inspect --stream-id 123
|
164
|
+
```
|
165
|
+
|
166
|
+
- Stopping or destroying a VM ends the stream:
|
167
|
+
|
168
|
+
```bash
|
169
|
+
# Stop VM and terminate payment stream (best-effort)
|
170
|
+
golem vm stop my-vm
|
171
|
+
|
172
|
+
# Destroy VM and terminate stream
|
173
|
+
golem vm destroy my-vm
|
131
174
|
```
|
132
175
|
|
133
176
|
- Create a VM and attach an existing stream (no auto-streams are created by the requestor):
|
134
177
|
|
135
178
|
```bash
|
136
|
-
|
179
|
+
golem vm create my-vm \
|
137
180
|
--provider-id 0xProvider \
|
138
181
|
--cpu 2 --memory 4 --storage 20 \
|
139
182
|
--stream-id 123
|
@@ -141,7 +184,8 @@ poetry run golem vm create my-vm \
|
|
141
184
|
|
142
185
|
Environment (env prefix `GOLEM_REQUESTOR_`):
|
143
186
|
|
144
|
-
- `
|
187
|
+
- `payments_network` — Payments network profile (defaults to `l2.holesky`). Profiles provide RPC + faucet defaults.
|
188
|
+
- `polygon_rpc_url` — EVM RPC URL (defaults from `payments_network` profile; can be overridden)
|
145
189
|
- `stream_payment_address` — StreamPayment address (defaults from `contracts/deployments/l2.json`; overridden by provider info)
|
146
190
|
- `glm_token_address` — Token address (defaults from `contracts/deployments/l2.json`; zero address means native ETH)
|
147
191
|
- Optional override of deployments directory: set `GOLEM_DEPLOYMENTS_DIR` to a folder containing `l2.json`.
|
@@ -154,16 +198,21 @@ Efficiency tips:
|
|
154
198
|
- Withdrawals are typically executed by providers; requestors don’t need to withdraw.
|
155
199
|
- The CLI `vm stream open` will prefer the provider’s advertised contract/token addresses to prevent mismatches.
|
156
200
|
|
157
|
-
|
201
|
+
Monitoring and auto top-up:
|
202
|
+
|
203
|
+
- The requestor API runs a background monitor that keeps each running VM’s stream funded with at least 1 hour runway (configurable). It checks every 30s and tops up to the target runway.
|
204
|
+
- Configure via env (prefix `GOLEM_REQUESTOR_`): `stream_monitor_enabled` (default true), `stream_monitor_interval_seconds` (default 30), `stream_min_remaining_seconds` (default 3600), `stream_topup_target_seconds` (default 3600).
|
205
|
+
|
206
|
+
## Faucet (Testnet only)
|
158
207
|
|
159
208
|
- Request L2 test ETH to cover stream transactions:
|
160
209
|
|
161
210
|
```bash
|
162
|
-
|
211
|
+
golem wallet faucet
|
163
212
|
```
|
164
213
|
|
165
214
|
- Defaults:
|
166
|
-
- Faucet
|
215
|
+
- Faucet URL and enablement come from the active `payments_network` profile. On `mainnet` (or other profiles without faucet) the command is disabled.
|
167
216
|
- CAPTCHA: `https://cap.gobas.me/05381a2cef5e`
|
168
217
|
- Override with env: `GOLEM_REQUESTOR_l2_faucet_url`, `GOLEM_REQUESTOR_captcha_url`, `GOLEM_REQUESTOR_captcha_api_key`.
|
169
218
|
|
@@ -171,7 +220,7 @@ poetry run golem wallet faucet
|
|
171
220
|
|
172
221
|
```bash
|
173
222
|
# Install using pip
|
174
|
-
pip install
|
223
|
+
pip install request-vm-on-golem
|
175
224
|
|
176
225
|
# Or install from source
|
177
226
|
git clone https://github.com/golem/vm-on-golem.git
|
@@ -193,11 +242,7 @@ First, source the development environment variables:
|
|
193
242
|
source .env.dev
|
194
243
|
```
|
195
244
|
|
196
|
-
Then, run any `golem` command. For example:
|
197
|
-
|
198
|
-
```bash
|
199
|
-
poetry run golem vm providers
|
200
|
-
```
|
245
|
+
Then, run any `golem` command. For example: `golem vm providers`
|
201
246
|
|
202
247
|
### Prepending variables
|
203
248
|
|
@@ -214,19 +259,15 @@ GOLEM_REQUESTOR_ENVIRONMENT="development" GOLEM_REQUESTOR_FORCE_LOCALHOST="true"
|
|
214
259
|
- Does not determine chain selection.
|
215
260
|
|
216
261
|
- Network Selection (`--network` or `GOLEM_REQUESTOR_NETWORK`)
|
217
|
-
- Filters
|
218
|
-
|
219
|
-
|
262
|
+
- Filters results by `testnet|mainnet`. Defaults are sensible; most users don’t need to change anything.
|
263
|
+
|
264
|
+
- Payments Network (`GOLEM_REQUESTOR_PAYMENTS_NETWORK`)
|
265
|
+
- Selects the payments chain profile (e.g., `l2.holesky`, `mainnet`) used for streaming payments; sets default RPC and faucet behavior.
|
266
|
+
- Provider discovery filters by this payments network via `vm providers` unless `--all-payments` is supplied. Override payments filter with `--payments-network <name>`.
|
220
267
|
|
221
268
|
Examples:
|
222
|
-
- List providers on mainnet without changing env:
|
223
|
-
|
224
|
-
poetry run golem vm providers --network mainnet
|
225
|
-
```
|
226
|
-
- Create a VM while targeting testnet:
|
227
|
-
```bash
|
228
|
-
poetry run golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20 --network testnet
|
229
|
-
```
|
269
|
+
- List providers on mainnet without changing env: `golem vm providers --network mainnet`
|
270
|
+
- Create a VM while targeting testnet: `golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20 --network testnet`
|
230
271
|
|
231
272
|
## Usage
|
232
273
|
|
@@ -332,9 +373,6 @@ The requestor uses a hierarchical configuration system:
|
|
332
373
|
1. Environment Variables:
|
333
374
|
|
334
375
|
```bash
|
335
|
-
# Discovery Service
|
336
|
-
export GOLEM_REQUESTOR_DISCOVERY_URL="http://discovery.golem.network:9001"
|
337
|
-
|
338
376
|
# Base Directory (default: ~/.golem)
|
339
377
|
export GOLEM_REQUESTOR_BASE_DIR="/path/to/golem/dir"
|
340
378
|
|
@@ -345,7 +383,7 @@ export GOLEM_REQUESTOR_DB_PATH="/path/to/database.db"
|
|
345
383
|
# Environment Mode (defaults to "production")
|
346
384
|
export GOLEM_REQUESTOR_ENVIRONMENT="development" # Optional: Switch to development mode
|
347
385
|
export GOLEM_REQUESTOR_FORCE_LOCALHOST="true" # Optional: Force localhost in development mode
|
348
|
-
export GOLEM_REQUESTOR_NETWORK="testnet" # Or "mainnet";
|
386
|
+
export GOLEM_REQUESTOR_NETWORK="testnet" # Or "mainnet"; optional filter for listing/creation
|
349
387
|
```
|
350
388
|
|
351
389
|
2. Directory Structure:
|
@@ -382,7 +420,7 @@ Local state is maintained in SQLite:
|
|
382
420
|
|
383
421
|
The requestor communicates with providers through:
|
384
422
|
|
385
|
-
1.
|
423
|
+
1. Network discovery (uses sane defaults; no setup required for most users)
|
386
424
|
2. Direct API calls for VM management
|
387
425
|
3. SSH proxy system for secure access
|
388
426
|
4. Resource tracking for capacity management
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "request-vm-on-golem"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.52"
|
4
4
|
description = "VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network"
|
5
5
|
authors = ["Phillip Jensen <phillip+vm-on-golem@golemgrid.com>"]
|
6
6
|
readme = "README.md"
|
@@ -5,11 +5,13 @@ from contextlib import asynccontextmanager
|
|
5
5
|
from ..services.database_service import DatabaseService
|
6
6
|
from ..config import config
|
7
7
|
from ..errors import DatabaseError
|
8
|
+
from ..payments.monitor import RequestorStreamMonitor
|
8
9
|
|
9
10
|
logger = logging.getLogger(__name__)
|
10
11
|
|
11
12
|
# Global variable to hold the database service instance
|
12
13
|
db_service: DatabaseService = None
|
14
|
+
stream_monitor: RequestorStreamMonitor | None = None
|
13
15
|
|
14
16
|
@asynccontextmanager
|
15
17
|
async def lifespan(app: FastAPI):
|
@@ -26,9 +28,15 @@ async def lifespan(app: FastAPI):
|
|
26
28
|
logger.error(f"Failed to initialize database during startup: {e}")
|
27
29
|
# Depending on requirements, you might want to prevent the app from starting
|
28
30
|
# raise RuntimeError(f"Database initialization failed: {e}") from e
|
31
|
+
# Start requestor stream monitor
|
32
|
+
global stream_monitor
|
33
|
+
stream_monitor = RequestorStreamMonitor(db_service)
|
34
|
+
stream_monitor.start()
|
29
35
|
yield
|
30
36
|
# Shutdown: Cleanup (if needed)
|
31
37
|
logger.info("Shutting down API.")
|
38
|
+
if stream_monitor:
|
39
|
+
await stream_monitor.stop()
|
32
40
|
# No explicit cleanup needed for aiosqlite connection usually
|
33
41
|
|
34
42
|
app = FastAPI(lifespan=lifespan)
|
@@ -56,4 +64,4 @@ async def list_vms():
|
|
56
64
|
# Example of another endpoint (can be removed if not needed)
|
57
65
|
@app.get("/")
|
58
66
|
async def read_root():
|
59
|
-
return {"message": "Golem Requestor API"}
|
67
|
+
return {"message": "Golem Requestor API"}
|
@@ -101,11 +101,13 @@ def vm():
|
|
101
101
|
@click.option('--storage', type=int, help='Minimum disk (GB) required')
|
102
102
|
@click.option('--country', help='Preferred provider country')
|
103
103
|
@click.option('--driver', type=click.Choice(['central', 'golem-base']), default=None, help='Discovery driver to use')
|
104
|
+
@click.option('--payments-network', type=str, default=None, help='Filter by payments network profile (default: current config)')
|
105
|
+
@click.option('--all-payments', is_flag=True, help='Do not filter by payments network (show all)')
|
104
106
|
@click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
|
105
107
|
@click.option('--network', type=click.Choice(['testnet', 'mainnet']), default=None,
|
106
108
|
help='Override network filter for this command')
|
107
109
|
@async_command
|
108
|
-
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], driver: Optional[str], as_json: bool, network: Optional[str] = None):
|
110
|
+
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], driver: Optional[str], payments_network: Optional[str] = None, all_payments: bool = False, as_json: bool = False, network: Optional[str] = None):
|
109
111
|
"""List available providers matching requirements."""
|
110
112
|
try:
|
111
113
|
if network:
|
@@ -124,7 +126,8 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
124
126
|
|
125
127
|
# Determine the discovery driver being used
|
126
128
|
discovery_driver = driver or config.discovery_driver
|
127
|
-
|
129
|
+
eff_pn = payments_network if payments_network is not None else getattr(config, 'payments_network', None)
|
130
|
+
logger.process(f"Querying discovery via {discovery_driver} (network={config.network}, payments={eff_pn if not all_payments else 'ALL'})")
|
128
131
|
|
129
132
|
# Initialize provider service
|
130
133
|
provider_service = ProviderService()
|
@@ -132,13 +135,21 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
132
135
|
# If a full spec is provided, enable per-provider estimate display
|
133
136
|
if cpu and memory and storage:
|
134
137
|
provider_service.estimate_spec = (cpu, memory, storage)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
try:
|
139
|
+
providers = await provider_service.find_providers(
|
140
|
+
cpu=cpu,
|
141
|
+
memory=memory,
|
142
|
+
storage=storage,
|
143
|
+
country=country,
|
144
|
+
driver=driver,
|
145
|
+
payments_network=eff_pn,
|
146
|
+
include_all_payments=bool(all_payments),
|
147
|
+
)
|
148
|
+
except TypeError:
|
149
|
+
# Backward compatibility with older/dummy service stubs in tests
|
150
|
+
providers = await provider_service.find_providers(
|
151
|
+
cpu=cpu, memory=memory, storage=storage, country=country, driver=driver
|
152
|
+
)
|
142
153
|
|
143
154
|
if not providers:
|
144
155
|
logger.warning("No providers found matching criteria")
|
@@ -606,6 +617,10 @@ def wallet():
|
|
606
617
|
async def wallet_faucet():
|
607
618
|
"""Request L2 faucet funds for the requestor's payment address."""
|
608
619
|
try:
|
620
|
+
if not getattr(config, 'faucet_enabled', False):
|
621
|
+
logger.warning("Faucet is disabled for the current payments network.")
|
622
|
+
click.echo(json.dumps({"error": "faucet_disabled", "network": getattr(config, 'payments_network', None)}, indent=2))
|
623
|
+
return
|
609
624
|
from ..security.faucet import L2FaucetService
|
610
625
|
from eth_account import Account
|
611
626
|
acct = Account.from_key(config.ethereum_private_key)
|
@@ -743,7 +758,16 @@ async def destroy_vm(name: str):
|
|
743
758
|
# Initialize VM service
|
744
759
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
745
760
|
async with ProviderClient(provider_url) as client:
|
746
|
-
|
761
|
+
# Initialize blockchain client for stream termination on destroy
|
762
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
763
|
+
spc = StreamPaymentConfig(
|
764
|
+
rpc_url=config.polygon_rpc_url,
|
765
|
+
contract_address=config.stream_payment_address,
|
766
|
+
glm_token_address=config.glm_token_address,
|
767
|
+
private_key=config.ethereum_private_key,
|
768
|
+
)
|
769
|
+
sp_client = StreamPaymentClient(spc)
|
770
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
747
771
|
await vm_service.destroy_vm(name)
|
748
772
|
|
749
773
|
# Show fancy success message
|
@@ -798,7 +822,16 @@ async def purge_vms(force: bool):
|
|
798
822
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
799
823
|
|
800
824
|
async with ProviderClient(provider_url) as client:
|
801
|
-
|
825
|
+
# Initialize blockchain client for stream termination on purge
|
826
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
827
|
+
spc = StreamPaymentConfig(
|
828
|
+
rpc_url=config.polygon_rpc_url,
|
829
|
+
contract_address=config.stream_payment_address,
|
830
|
+
glm_token_address=config.glm_token_address,
|
831
|
+
private_key=config.ethereum_private_key,
|
832
|
+
)
|
833
|
+
sp_client = StreamPaymentClient(spc)
|
834
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
802
835
|
await vm_service.destroy_vm(vm['name'])
|
803
836
|
results['success'].append((vm['name'], 'Destroyed successfully'))
|
804
837
|
|
@@ -921,7 +954,16 @@ async def stop_vm(name: str):
|
|
921
954
|
# Initialize VM service
|
922
955
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
923
956
|
async with ProviderClient(provider_url) as client:
|
924
|
-
|
957
|
+
# Initialize blockchain client for stream termination on stop
|
958
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
959
|
+
spc = StreamPaymentConfig(
|
960
|
+
rpc_url=config.polygon_rpc_url,
|
961
|
+
contract_address=config.stream_payment_address,
|
962
|
+
glm_token_address=config.glm_token_address,
|
963
|
+
private_key=config.ethereum_private_key,
|
964
|
+
)
|
965
|
+
sp_client = StreamPaymentClient(spc)
|
966
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
925
967
|
await vm_service.stop_vm(name)
|
926
968
|
|
927
969
|
# Show fancy success message
|
@@ -54,6 +54,13 @@ class RequestorConfig(BaseSettings):
|
|
54
54
|
description="Target network: 'testnet' or 'mainnet'"
|
55
55
|
)
|
56
56
|
|
57
|
+
# Payments chain selection (modular network profiles)
|
58
|
+
# Keep current standard as l2.holesky
|
59
|
+
payments_network: str = Field(
|
60
|
+
default="l2.holesky",
|
61
|
+
description="Payments network profile (e.g., 'l2.holesky', 'kaolin.holesky', 'mainnet')"
|
62
|
+
)
|
63
|
+
|
57
64
|
# Development Settings
|
58
65
|
force_localhost: bool = Field(
|
59
66
|
default=False,
|
@@ -102,8 +109,8 @@ class RequestorConfig(BaseSettings):
|
|
102
109
|
|
103
110
|
# Payments (EVM RPC)
|
104
111
|
polygon_rpc_url: str = Field(
|
105
|
-
default="
|
106
|
-
description="EVM RPC URL for streaming payments
|
112
|
+
default="",
|
113
|
+
description="EVM RPC URL for streaming payments; defaults from payments_network profile"
|
107
114
|
)
|
108
115
|
stream_payment_address: str = Field(
|
109
116
|
default="",
|
@@ -113,10 +120,27 @@ class RequestorConfig(BaseSettings):
|
|
113
120
|
default="",
|
114
121
|
description="Token address (0x0 means native ETH). Defaults from l2.json"
|
115
122
|
)
|
116
|
-
#
|
123
|
+
# Stream monitor (auto top-up)
|
124
|
+
stream_monitor_enabled: bool = Field(
|
125
|
+
default=True,
|
126
|
+
description="Enable background monitor to auto top-up streams"
|
127
|
+
)
|
128
|
+
stream_monitor_interval_seconds: int = Field(
|
129
|
+
default=30,
|
130
|
+
description="How frequently to check and top up streams"
|
131
|
+
)
|
132
|
+
stream_min_remaining_seconds: int = Field(
|
133
|
+
default=3600,
|
134
|
+
description="Minimum remaining runway to maintain (seconds)"
|
135
|
+
)
|
136
|
+
stream_topup_target_seconds: int = Field(
|
137
|
+
default=3600,
|
138
|
+
description="Target runway after top-up (seconds)"
|
139
|
+
)
|
140
|
+
# Faucet settings (payments)
|
117
141
|
l2_faucet_url: str = Field(
|
118
|
-
default="
|
119
|
-
description="
|
142
|
+
default="",
|
143
|
+
description="Faucet base URL (no trailing /api). Only used on testnets. Defaults from payments_network profile"
|
120
144
|
)
|
121
145
|
captcha_url: str = Field(
|
122
146
|
default="https://cap.gobas.me",
|
@@ -133,8 +157,8 @@ class RequestorConfig(BaseSettings):
|
|
133
157
|
|
134
158
|
@field_validator("polygon_rpc_url", mode='before')
|
135
159
|
@classmethod
|
136
|
-
def prefer_alt_env(cls, v: str) -> str:
|
137
|
-
# Accept alt aliases
|
160
|
+
def prefer_alt_env(cls, v: str, info: ValidationInfo) -> str:
|
161
|
+
# Accept alt aliases overriding the profile
|
138
162
|
for key in (
|
139
163
|
"GOLEM_REQUESTOR_l2_rpc_url",
|
140
164
|
"GOLEM_REQUESTOR_L2_RPC_URL",
|
@@ -143,22 +167,45 @@ class RequestorConfig(BaseSettings):
|
|
143
167
|
):
|
144
168
|
if os.environ.get(key):
|
145
169
|
return os.environ[key]
|
146
|
-
|
170
|
+
if v:
|
171
|
+
return v
|
172
|
+
# Default from payments profile
|
173
|
+
pn = info.data.get("payments_network") or "l2.holesky"
|
174
|
+
return RequestorConfig._profile_defaults(pn)["rpc_url"]
|
175
|
+
|
176
|
+
@field_validator("l2_faucet_url", mode='before')
|
177
|
+
@classmethod
|
178
|
+
def default_faucet_env(cls, v: str, info: ValidationInfo) -> str:
|
179
|
+
for key in (
|
180
|
+
"GOLEM_REQUESTOR_l2_faucet_url",
|
181
|
+
"GOLEM_REQUESTOR_L2_FAUCET_URL",
|
182
|
+
):
|
183
|
+
if os.environ.get(key):
|
184
|
+
return os.environ[key]
|
185
|
+
if v:
|
186
|
+
return v
|
187
|
+
pn = info.data.get("payments_network") or "l2.holesky"
|
188
|
+
return RequestorConfig._profile_defaults(pn).get("faucet_url", "")
|
147
189
|
|
148
190
|
@staticmethod
|
149
|
-
def
|
191
|
+
def _load_deployment(network: str) -> tuple[str | None, str | None]:
|
150
192
|
try:
|
151
193
|
base = os.environ.get("GOLEM_DEPLOYMENTS_DIR")
|
152
194
|
if base:
|
153
|
-
path = Path(base) / "
|
195
|
+
path = Path(base) / f"{RequestorConfig._deployment_basename(network)}.json"
|
154
196
|
else:
|
155
197
|
# repo root assumption: ../../ relative to this file
|
156
|
-
path =
|
198
|
+
path = (
|
199
|
+
Path(__file__).resolve().parents[2]
|
200
|
+
/ "contracts" / "deployments" / f"{RequestorConfig._deployment_basename(network)}.json"
|
201
|
+
)
|
157
202
|
if not path.exists():
|
158
203
|
# Try package resource fallback
|
159
204
|
try:
|
160
205
|
import importlib.resources as ir
|
161
|
-
with ir.files("requestor.data.deployments").joinpath(
|
206
|
+
with ir.files("requestor.data.deployments").joinpath(
|
207
|
+
f"{RequestorConfig._deployment_basename(network)}.json"
|
208
|
+
).open("r") as fh: # type: ignore[attr-defined]
|
162
209
|
import json as _json
|
163
210
|
data = _json.load(fh)
|
164
211
|
except Exception:
|
@@ -175,22 +222,83 @@ class RequestorConfig(BaseSettings):
|
|
175
222
|
pass
|
176
223
|
return None, None
|
177
224
|
|
225
|
+
@staticmethod
|
226
|
+
def _deployment_basename(network: str) -> str:
|
227
|
+
# Map well-known network aliases to deployment file base names
|
228
|
+
n = (network or "").lower()
|
229
|
+
if n in ("l2", "l2.holesky"): # current standard
|
230
|
+
return "l2"
|
231
|
+
if "." in n:
|
232
|
+
return n.split(".")[0]
|
233
|
+
return n or "l2"
|
234
|
+
|
235
|
+
@staticmethod
|
236
|
+
def _profile_defaults(network: str) -> Dict[str, str]:
|
237
|
+
n = (network or "l2.holesky").lower()
|
238
|
+
# Built-in profiles; extend easily in future
|
239
|
+
profiles = {
|
240
|
+
"l2.holesky": {
|
241
|
+
"rpc_url": "https://l2.holesky.golemdb.io/rpc",
|
242
|
+
"faucet_url": "https://l2.holesky.golemdb.io/faucet",
|
243
|
+
"faucet_enabled": True,
|
244
|
+
"token_symbol": "GLM",
|
245
|
+
"gas_symbol": "ETH",
|
246
|
+
},
|
247
|
+
# Example: mainnet has no faucet by default
|
248
|
+
"mainnet": {
|
249
|
+
"rpc_url": "",
|
250
|
+
"faucet_url": "",
|
251
|
+
"faucet_enabled": False,
|
252
|
+
"token_symbol": "GLM",
|
253
|
+
"gas_symbol": "ETH",
|
254
|
+
},
|
255
|
+
}
|
256
|
+
return profiles.get(n, profiles["l2.holesky"]) # default to current standard
|
257
|
+
|
178
258
|
@field_validator("stream_payment_address", mode='before')
|
179
259
|
@classmethod
|
180
|
-
def default_stream_addr(cls, v: str) -> str:
|
260
|
+
def default_stream_addr(cls, v: str, info: ValidationInfo) -> str:
|
181
261
|
if v:
|
182
262
|
return v
|
183
|
-
|
263
|
+
network = info.data.get("payments_network") or "l2.holesky"
|
264
|
+
addr, _ = RequestorConfig._load_deployment(network)
|
184
265
|
return addr or "0x0000000000000000000000000000000000000000"
|
185
266
|
|
186
267
|
@field_validator("glm_token_address", mode='before')
|
187
268
|
@classmethod
|
188
|
-
def default_token_addr(cls, v: str) -> str:
|
269
|
+
def default_token_addr(cls, v: str, info: ValidationInfo) -> str:
|
189
270
|
if v:
|
190
271
|
return v
|
191
|
-
|
272
|
+
network = info.data.get("payments_network") or "l2.holesky"
|
273
|
+
_, token = RequestorConfig._load_deployment(network)
|
192
274
|
return token or "0x0000000000000000000000000000000000000000"
|
193
275
|
|
276
|
+
# Optional convenience: expose token and gas symbols based on profile
|
277
|
+
token_symbol: str = Field(
|
278
|
+
default="",
|
279
|
+
description="Human-friendly symbol of payment token (e.g., GLM)"
|
280
|
+
)
|
281
|
+
gas_token_symbol: str = Field(
|
282
|
+
default="",
|
283
|
+
description="Symbol of gas token for the chain (e.g., ETH)"
|
284
|
+
)
|
285
|
+
|
286
|
+
@field_validator("token_symbol", mode="before")
|
287
|
+
@classmethod
|
288
|
+
def default_token_symbol(cls, v: str, info: ValidationInfo) -> str:
|
289
|
+
if v:
|
290
|
+
return v
|
291
|
+
pn = info.data.get("payments_network") or "l2.holesky"
|
292
|
+
return RequestorConfig._profile_defaults(pn).get("token_symbol", "")
|
293
|
+
|
294
|
+
@field_validator("gas_token_symbol", mode="before")
|
295
|
+
@classmethod
|
296
|
+
def default_gas_symbol(cls, v: str, info: ValidationInfo) -> str:
|
297
|
+
if v:
|
298
|
+
return v
|
299
|
+
pn = info.data.get("payments_network") or "l2.holesky"
|
300
|
+
return RequestorConfig._profile_defaults(pn).get("gas_symbol", "")
|
301
|
+
|
194
302
|
# Base Directory
|
195
303
|
base_dir: Path = Field(
|
196
304
|
default_factory=lambda: Path.home() / ".golem" / "requestor",
|
@@ -223,6 +331,11 @@ class RequestorConfig(BaseSettings):
|
|
223
331
|
kwargs['db_path'] = base_dir / "vms.db"
|
224
332
|
super().__init__(**kwargs)
|
225
333
|
|
334
|
+
@property
|
335
|
+
def faucet_enabled(self) -> bool:
|
336
|
+
"""Whether requesting funds from faucet is allowed for current payments network."""
|
337
|
+
return bool(self._profile_defaults(self.payments_network).get("faucet_enabled", False))
|
338
|
+
|
226
339
|
def get_provider_url(self, ip_address: str) -> str:
|
227
340
|
"""Get provider API URL.
|
228
341
|
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from ..services.database_service import DatabaseService
|
5
|
+
from ..provider.client import ProviderClient
|
6
|
+
from ..config import config
|
7
|
+
from ..utils.logging import setup_logger
|
8
|
+
from .blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
9
|
+
|
10
|
+
|
11
|
+
class RequestorStreamMonitor:
|
12
|
+
def __init__(self, db: DatabaseService):
|
13
|
+
self.db = db
|
14
|
+
self._task: Optional[asyncio.Task] = None
|
15
|
+
self._logger = setup_logger(__name__)
|
16
|
+
self._sp = StreamPaymentClient(
|
17
|
+
StreamPaymentConfig(
|
18
|
+
rpc_url=config.polygon_rpc_url,
|
19
|
+
contract_address=config.stream_payment_address,
|
20
|
+
glm_token_address=config.glm_token_address,
|
21
|
+
private_key=config.ethereum_private_key,
|
22
|
+
)
|
23
|
+
)
|
24
|
+
|
25
|
+
def start(self):
|
26
|
+
if not config.stream_monitor_enabled:
|
27
|
+
return
|
28
|
+
self._logger.info(
|
29
|
+
f"⏱️ Requestor stream auto-topup enabled interval={config.stream_monitor_interval_seconds}s "
|
30
|
+
f"min_remaining={config.stream_min_remaining_seconds}s target={config.stream_topup_target_seconds}s"
|
31
|
+
)
|
32
|
+
self._task = asyncio.create_task(self._run(), name="requestor-stream-monitor")
|
33
|
+
|
34
|
+
async def stop(self):
|
35
|
+
if self._task:
|
36
|
+
self._task.cancel()
|
37
|
+
try:
|
38
|
+
await self._task
|
39
|
+
except asyncio.CancelledError:
|
40
|
+
pass
|
41
|
+
self._logger.info("Requestor stream auto-topup stopped")
|
42
|
+
|
43
|
+
async def _resolve_stream_id(self, vm: dict) -> Optional[int]:
|
44
|
+
# Prefer local DB recorded stream_id
|
45
|
+
sid = vm.get("config", {}).get("stream_id")
|
46
|
+
if isinstance(sid, int):
|
47
|
+
return sid
|
48
|
+
# Ask provider for mapping
|
49
|
+
try:
|
50
|
+
provider_url = config.get_provider_url(vm["provider_ip"])
|
51
|
+
async with ProviderClient(provider_url) as client:
|
52
|
+
status = await client.get_vm_stream_status(vm["vm_id"])
|
53
|
+
sid = status.get("stream_id")
|
54
|
+
self._logger.debug(f"Resolved stream for VM {vm['name']}: {sid}")
|
55
|
+
return int(sid) if sid is not None else None
|
56
|
+
except Exception as e:
|
57
|
+
self._logger.debug(f"Could not resolve stream for VM {vm['name']}: {e}")
|
58
|
+
return None
|
59
|
+
|
60
|
+
async def _run(self):
|
61
|
+
interval = max(int(config.stream_monitor_interval_seconds), 5)
|
62
|
+
min_remaining = max(int(config.stream_min_remaining_seconds), 0)
|
63
|
+
target_seconds = max(int(config.stream_topup_target_seconds), min_remaining)
|
64
|
+
while True:
|
65
|
+
try:
|
66
|
+
vms = await self.db.list_vms()
|
67
|
+
self._logger.debug(f"stream monitor tick: {len(vms)} VMs to check")
|
68
|
+
for vm in vms:
|
69
|
+
# Only manage running VMs
|
70
|
+
if vm.get("status") != "running":
|
71
|
+
self._logger.debug(f"skip VM {vm.get('name')} status={vm.get('status')}")
|
72
|
+
continue
|
73
|
+
stream_id = await self._resolve_stream_id(vm)
|
74
|
+
if stream_id is None:
|
75
|
+
self._logger.debug(f"skip VM {vm.get('name')} no stream mapped")
|
76
|
+
continue
|
77
|
+
# Read on-chain stream tuple via contract
|
78
|
+
try:
|
79
|
+
token, sender, recipient, startTime, stopTime, ratePerSecond, deposit, withdrawn, halted = (
|
80
|
+
self._sp.contract.functions.streams(int(stream_id)).call()
|
81
|
+
)
|
82
|
+
except Exception as e:
|
83
|
+
self._logger.warning(f"stream lookup failed for {stream_id}: {e}")
|
84
|
+
continue
|
85
|
+
if bool(halted):
|
86
|
+
# Respect terminated streams
|
87
|
+
self._logger.debug(f"skip stream {stream_id} halted=true")
|
88
|
+
continue
|
89
|
+
# Compute remaining seconds using chain time
|
90
|
+
try:
|
91
|
+
now = int(self._sp.web3.eth.get_block("latest")["timestamp"])
|
92
|
+
except Exception as e:
|
93
|
+
self._logger.warning(f"could not get chain time: {e}")
|
94
|
+
continue
|
95
|
+
remaining = max(int(stopTime) - now, 0)
|
96
|
+
self._logger.debug(
|
97
|
+
f"VM {vm.get('name')} stream {stream_id}: remaining={remaining}s rate={int(ratePerSecond)}"
|
98
|
+
)
|
99
|
+
if remaining < min_remaining:
|
100
|
+
# Top up to reach target_seconds of runway
|
101
|
+
deficit = max(target_seconds - remaining, 0)
|
102
|
+
add_wei = int(deficit) * int(ratePerSecond)
|
103
|
+
if add_wei <= 0:
|
104
|
+
continue
|
105
|
+
try:
|
106
|
+
self._logger.info(
|
107
|
+
f"⛽ topping up stream {stream_id} by {add_wei} wei to reach {target_seconds}s"
|
108
|
+
)
|
109
|
+
self._sp.top_up(int(stream_id), int(add_wei))
|
110
|
+
self._logger.success(
|
111
|
+
f"topped up stream {stream_id} (+{add_wei} wei); VM={vm.get('name')}"
|
112
|
+
)
|
113
|
+
except Exception as e:
|
114
|
+
# Ignore failures; will retry next tick
|
115
|
+
self._logger.warning(f"top-up failed for stream {stream_id}: {e}")
|
116
|
+
else:
|
117
|
+
self._logger.debug(
|
118
|
+
f"stream {stream_id} healthy (remaining={remaining}s >= {min_remaining}s)"
|
119
|
+
)
|
120
|
+
await asyncio.sleep(interval)
|
121
|
+
except asyncio.CancelledError:
|
122
|
+
break
|
123
|
+
except Exception as e:
|
124
|
+
# Keep the monitor resilient
|
125
|
+
self._logger.error(f"requestor stream monitor error: {e}")
|
126
|
+
await asyncio.sleep(interval)
|
@@ -15,7 +15,7 @@ class L2FaucetService:
|
|
15
15
|
self.cfg = config
|
16
16
|
self.web3 = Web3(Web3.HTTPProvider(config.polygon_rpc_url))
|
17
17
|
self.client = PowFaucetClient(
|
18
|
-
faucet_url=getattr(config, 'l2_faucet_url', 'https://l2.holesky.golemdb.io/faucet'
|
18
|
+
faucet_url=getattr(config, 'l2_faucet_url', '') or 'https://l2.holesky.golemdb.io/faucet',
|
19
19
|
captcha_base_url=getattr(config, 'captcha_url', 'https://cap.gobas.me'),
|
20
20
|
captcha_api_key=getattr(config, 'captcha_api_key', '05381a2cef5e'),
|
21
21
|
)
|
@@ -29,6 +29,10 @@ class L2FaucetService:
|
|
29
29
|
return 0.0
|
30
30
|
|
31
31
|
async def request_funds(self, address: str) -> Optional[str]:
|
32
|
+
# Disallow faucet if explicitly disabled by profile
|
33
|
+
if hasattr(self.cfg, 'faucet_enabled') and not getattr(self.cfg, 'faucet_enabled'):
|
34
|
+
logger.info("Faucet disabled for current payments network; skipping.")
|
35
|
+
return None
|
32
36
|
bal = self._balance_eth(address)
|
33
37
|
if bal > 0.01:
|
34
38
|
logger.info(f"Sufficient L2 funds ({bal} ETH), skipping faucet.")
|
@@ -51,4 +55,3 @@ class L2FaucetService:
|
|
51
55
|
if tx:
|
52
56
|
logger.success(f"L2 faucet sent tx: {tx}")
|
53
57
|
return tx
|
54
|
-
|
{request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/provider_service.py
RENAMED
@@ -66,7 +66,9 @@ class ProviderService:
|
|
66
66
|
memory: Optional[int] = None,
|
67
67
|
storage: Optional[int] = None,
|
68
68
|
country: Optional[str] = None,
|
69
|
-
driver: Optional[str] = None
|
69
|
+
driver: Optional[str] = None,
|
70
|
+
payments_network: Optional[str] = None,
|
71
|
+
include_all_payments: bool = False,
|
70
72
|
) -> List[Dict]:
|
71
73
|
"""Find providers matching requirements."""
|
72
74
|
discovery_driver = driver or config.discovery_driver
|
@@ -79,7 +81,11 @@ class ProviderService:
|
|
79
81
|
ws_url=config.golem_base_ws_url,
|
80
82
|
private_key=private_key_bytes,
|
81
83
|
)
|
82
|
-
return await self._find_providers_golem_base(
|
84
|
+
return await self._find_providers_golem_base(
|
85
|
+
cpu, memory, storage, country,
|
86
|
+
payments_network=payments_network,
|
87
|
+
include_all_payments=include_all_payments,
|
88
|
+
)
|
83
89
|
else:
|
84
90
|
return await self._find_providers_central(cpu, memory, storage, country)
|
85
91
|
|
@@ -88,7 +94,9 @@ class ProviderService:
|
|
88
94
|
cpu: Optional[int] = None,
|
89
95
|
memory: Optional[int] = None,
|
90
96
|
storage: Optional[int] = None,
|
91
|
-
country: Optional[str] = None
|
97
|
+
country: Optional[str] = None,
|
98
|
+
payments_network: Optional[str] = None,
|
99
|
+
include_all_payments: bool = False,
|
92
100
|
) -> List[Dict]:
|
93
101
|
"""Find providers using Golem Base."""
|
94
102
|
try:
|
@@ -104,6 +112,10 @@ class ProviderService:
|
|
104
112
|
# Filter by advertised network to avoid cross-network results
|
105
113
|
if config.network:
|
106
114
|
query += f' && golem_network="{config.network}"'
|
115
|
+
# Filter by payments network unless explicitly disabled
|
116
|
+
pn = payments_network if payments_network is not None else getattr(config, 'payments_network', None)
|
117
|
+
if pn and not include_all_payments:
|
118
|
+
query += f' && golem_payments_network="{pn}"'
|
107
119
|
if cpu:
|
108
120
|
query += f' && golem_cpu>={cpu}'
|
109
121
|
if memory:
|
@@ -130,6 +142,7 @@ class ProviderService:
|
|
130
142
|
'provider_name': annotations.get('golem_provider_name'),
|
131
143
|
'ip_address': annotations.get('golem_ip_address'),
|
132
144
|
'country': annotations.get('golem_country'),
|
145
|
+
'payments_network': annotations.get('golem_payments_network'),
|
133
146
|
'resources': {
|
134
147
|
'cpu': int(annotations.get('golem_cpu', 0)),
|
135
148
|
'memory': int(annotations.get('golem_memory', 0)),
|
@@ -142,11 +142,11 @@ class VMService:
|
|
142
142
|
# Update status in database
|
143
143
|
await self.db.update_vm_status(name, "stopped")
|
144
144
|
|
145
|
-
# Best-effort
|
145
|
+
# Best-effort terminate stream on stop (treat stop as end of agreement)
|
146
146
|
try:
|
147
147
|
stream_id = vm.get('config', {}).get('stream_id')
|
148
148
|
if stream_id is not None and self.blockchain_client:
|
149
|
-
self.blockchain_client.
|
149
|
+
self.blockchain_client.terminate(stream_id)
|
150
150
|
except Exception:
|
151
151
|
pass
|
152
152
|
|
File without changes
|
File without changes
|
{request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/data/deployments/l2.json
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/payments/blockchain_service.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{request_vm_on_golem-0.1.50 → request_vm_on_golem-0.1.52}/requestor/services/database_service.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|