request-vm-on-golem 0.1.49__tar.gz → 0.1.51__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.49 → request_vm_on_golem-0.1.51}/PKG-INFO +16 -1
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/README.md +15 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/pyproject.toml +1 -1
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/api/main.py +9 -1
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/cli/commands.py +114 -3
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/config.py +17 -0
- request_vm_on_golem-0.1.51/requestor/payments/monitor.py +126 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/vm_service.py +2 -2
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/cli/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/data/deployments/l2.json +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/db/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/db/sqlite.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/errors.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/payments/blockchain_service.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/provider/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/provider/client.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/run.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/security/faucet.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/database_service.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/provider_service.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/ssh_service.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/ssh/__init__.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/ssh/manager.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/utils/logging.py +0 -0
- {request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/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.51
|
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
|
@@ -171,6 +171,16 @@ poetry run golem vm stream status my-vm
|
|
171
171
|
poetry run golem vm stream inspect --stream-id 123
|
172
172
|
```
|
173
173
|
|
174
|
+
- Stopping or destroying a VM ends the stream:
|
175
|
+
|
176
|
+
```bash
|
177
|
+
# Stop VM and terminate payment stream (best-effort)
|
178
|
+
poetry run golem vm stop my-vm
|
179
|
+
|
180
|
+
# Destroy VM and terminate stream
|
181
|
+
poetry run golem vm destroy my-vm
|
182
|
+
```
|
183
|
+
|
174
184
|
- Create a VM and attach an existing stream (no auto-streams are created by the requestor):
|
175
185
|
|
176
186
|
```bash
|
@@ -195,6 +205,11 @@ Efficiency tips:
|
|
195
205
|
- Withdrawals are typically executed by providers; requestors don’t need to withdraw.
|
196
206
|
- The CLI `vm stream open` will prefer the provider’s advertised contract/token addresses to prevent mismatches.
|
197
207
|
|
208
|
+
Monitoring and auto top-up:
|
209
|
+
|
210
|
+
- 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.
|
211
|
+
- 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).
|
212
|
+
|
198
213
|
## Faucet (L2 ETH)
|
199
214
|
|
200
215
|
- Request L2 test ETH to cover stream transactions:
|
@@ -130,6 +130,16 @@ poetry run golem vm stream status my-vm
|
|
130
130
|
poetry run golem vm stream inspect --stream-id 123
|
131
131
|
```
|
132
132
|
|
133
|
+
- Stopping or destroying a VM ends the stream:
|
134
|
+
|
135
|
+
```bash
|
136
|
+
# Stop VM and terminate payment stream (best-effort)
|
137
|
+
poetry run golem vm stop my-vm
|
138
|
+
|
139
|
+
# Destroy VM and terminate stream
|
140
|
+
poetry run golem vm destroy my-vm
|
141
|
+
```
|
142
|
+
|
133
143
|
- Create a VM and attach an existing stream (no auto-streams are created by the requestor):
|
134
144
|
|
135
145
|
```bash
|
@@ -154,6 +164,11 @@ Efficiency tips:
|
|
154
164
|
- Withdrawals are typically executed by providers; requestors don’t need to withdraw.
|
155
165
|
- The CLI `vm stream open` will prefer the provider’s advertised contract/token addresses to prevent mismatches.
|
156
166
|
|
167
|
+
Monitoring and auto top-up:
|
168
|
+
|
169
|
+
- 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.
|
170
|
+
- 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).
|
171
|
+
|
157
172
|
## Faucet (L2 ETH)
|
158
173
|
|
159
174
|
- Request L2 test ETH to cover stream transactions:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "request-vm-on-golem"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.51"
|
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"}
|
@@ -340,6 +340,90 @@ def vm_stream():
|
|
340
340
|
pass
|
341
341
|
|
342
342
|
|
343
|
+
@vm_stream.command('list')
|
344
|
+
@click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
|
345
|
+
@async_command
|
346
|
+
async def stream_list(as_json: bool):
|
347
|
+
"""List payment stream status for all known VMs."""
|
348
|
+
try:
|
349
|
+
vms = await db_service.list_vms()
|
350
|
+
if not vms:
|
351
|
+
logger.warning("No VMs found in local database")
|
352
|
+
click.echo(json.dumps({"streams": []}, indent=2) if as_json else "No VMs found.")
|
353
|
+
return {"streams": []}
|
354
|
+
|
355
|
+
results = []
|
356
|
+
for vm in vms:
|
357
|
+
item: dict = {
|
358
|
+
"name": vm.get("name"),
|
359
|
+
"provider_ip": vm.get("provider_ip"),
|
360
|
+
"stream_id": None,
|
361
|
+
"verified": False,
|
362
|
+
"reason": None,
|
363
|
+
"computed": {},
|
364
|
+
"error": None,
|
365
|
+
}
|
366
|
+
try:
|
367
|
+
provider_url = config.get_provider_url(vm['provider_ip'])
|
368
|
+
async with ProviderClient(provider_url) as client:
|
369
|
+
status = await client.get_vm_stream_status(vm['vm_id'])
|
370
|
+
item.update({
|
371
|
+
"stream_id": status.get("stream_id"),
|
372
|
+
"verified": bool(status.get("verified")),
|
373
|
+
"reason": status.get("reason"),
|
374
|
+
"computed": status.get("computed", {}),
|
375
|
+
})
|
376
|
+
except Exception as e:
|
377
|
+
msg = str(e)
|
378
|
+
# Normalize common provider errors
|
379
|
+
if "no stream mapped" in msg.lower():
|
380
|
+
item.update({
|
381
|
+
"stream_id": None,
|
382
|
+
"verified": False,
|
383
|
+
"reason": "unmapped",
|
384
|
+
})
|
385
|
+
else:
|
386
|
+
item["error"] = msg
|
387
|
+
results.append(item)
|
388
|
+
|
389
|
+
out = {"streams": results}
|
390
|
+
|
391
|
+
if as_json:
|
392
|
+
click.echo(json.dumps(out, indent=2))
|
393
|
+
else:
|
394
|
+
# Render a concise table
|
395
|
+
headers = [
|
396
|
+
"VM",
|
397
|
+
"Stream ID",
|
398
|
+
"Verified",
|
399
|
+
"Reason",
|
400
|
+
"Remaining (s)",
|
401
|
+
"Withdrawable (wei)",
|
402
|
+
]
|
403
|
+
rows = []
|
404
|
+
for r in results:
|
405
|
+
comp = r.get("computed") or {}
|
406
|
+
rows.append([
|
407
|
+
r.get("name"),
|
408
|
+
r.get("stream_id") if r.get("stream_id") is not None else "—",
|
409
|
+
"✔" if r.get("verified") else "✖",
|
410
|
+
r.get("reason") or ("error: " + r.get("error") if r.get("error") else ""),
|
411
|
+
comp.get("remaining_seconds", ""),
|
412
|
+
comp.get("withdrawable_wei", ""),
|
413
|
+
])
|
414
|
+
click.echo("\n" + "─" * 60)
|
415
|
+
click.echo(click.style(f" 💸 Streams ({len(results)} VMs)", fg="blue", bold=True))
|
416
|
+
click.echo("─" * 60)
|
417
|
+
click.echo("\n" + tabulate(rows, headers=[click.style(h, bold=True) for h in headers], tablefmt="grid"))
|
418
|
+
click.echo("\n" + "─" * 60)
|
419
|
+
|
420
|
+
return out
|
421
|
+
|
422
|
+
except Exception as e:
|
423
|
+
logger.error(f"Failed to list streams: {e}")
|
424
|
+
raise click.Abort()
|
425
|
+
|
426
|
+
|
343
427
|
@vm_stream.command('open')
|
344
428
|
@click.option('--provider-id', required=True, help='Provider ID to use')
|
345
429
|
@click.option('--cpu', type=int, required=True, help='CPU cores for rate calc')
|
@@ -659,7 +743,16 @@ async def destroy_vm(name: str):
|
|
659
743
|
# Initialize VM service
|
660
744
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
661
745
|
async with ProviderClient(provider_url) as client:
|
662
|
-
|
746
|
+
# Initialize blockchain client for stream termination on destroy
|
747
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
748
|
+
spc = StreamPaymentConfig(
|
749
|
+
rpc_url=config.polygon_rpc_url,
|
750
|
+
contract_address=config.stream_payment_address,
|
751
|
+
glm_token_address=config.glm_token_address,
|
752
|
+
private_key=config.ethereum_private_key,
|
753
|
+
)
|
754
|
+
sp_client = StreamPaymentClient(spc)
|
755
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
663
756
|
await vm_service.destroy_vm(name)
|
664
757
|
|
665
758
|
# Show fancy success message
|
@@ -714,7 +807,16 @@ async def purge_vms(force: bool):
|
|
714
807
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
715
808
|
|
716
809
|
async with ProviderClient(provider_url) as client:
|
717
|
-
|
810
|
+
# Initialize blockchain client for stream termination on purge
|
811
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
812
|
+
spc = StreamPaymentConfig(
|
813
|
+
rpc_url=config.polygon_rpc_url,
|
814
|
+
contract_address=config.stream_payment_address,
|
815
|
+
glm_token_address=config.glm_token_address,
|
816
|
+
private_key=config.ethereum_private_key,
|
817
|
+
)
|
818
|
+
sp_client = StreamPaymentClient(spc)
|
819
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
718
820
|
await vm_service.destroy_vm(vm['name'])
|
719
821
|
results['success'].append((vm['name'], 'Destroyed successfully'))
|
720
822
|
|
@@ -837,7 +939,16 @@ async def stop_vm(name: str):
|
|
837
939
|
# Initialize VM service
|
838
940
|
provider_url = config.get_provider_url(vm['provider_ip'])
|
839
941
|
async with ProviderClient(provider_url) as client:
|
840
|
-
|
942
|
+
# Initialize blockchain client for stream termination on stop
|
943
|
+
from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
|
944
|
+
spc = StreamPaymentConfig(
|
945
|
+
rpc_url=config.polygon_rpc_url,
|
946
|
+
contract_address=config.stream_payment_address,
|
947
|
+
glm_token_address=config.glm_token_address,
|
948
|
+
private_key=config.ethereum_private_key,
|
949
|
+
)
|
950
|
+
sp_client = StreamPaymentClient(spc)
|
951
|
+
vm_service = VMService(db_service, SSHService(config.ssh_key_dir), client, sp_client)
|
841
952
|
await vm_service.stop_vm(name)
|
842
953
|
|
843
954
|
# Show fancy success message
|
@@ -113,6 +113,23 @@ class RequestorConfig(BaseSettings):
|
|
113
113
|
default="",
|
114
114
|
description="Token address (0x0 means native ETH). Defaults from l2.json"
|
115
115
|
)
|
116
|
+
# Stream monitor (auto top-up)
|
117
|
+
stream_monitor_enabled: bool = Field(
|
118
|
+
default=True,
|
119
|
+
description="Enable background monitor to auto top-up streams"
|
120
|
+
)
|
121
|
+
stream_monitor_interval_seconds: int = Field(
|
122
|
+
default=30,
|
123
|
+
description="How frequently to check and top up streams"
|
124
|
+
)
|
125
|
+
stream_min_remaining_seconds: int = Field(
|
126
|
+
default=3600,
|
127
|
+
description="Minimum remaining runway to maintain (seconds)"
|
128
|
+
)
|
129
|
+
stream_topup_target_seconds: int = Field(
|
130
|
+
default=3600,
|
131
|
+
description="Target runway after top-up (seconds)"
|
132
|
+
)
|
116
133
|
# Faucet settings (L2 payments)
|
117
134
|
l2_faucet_url: str = Field(
|
118
135
|
default="https://l2.holesky.golemdb.io/faucet",
|
@@ -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)
|
@@ -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.49 → request_vm_on_golem-0.1.51}/requestor/data/deployments/l2.json
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/payments/blockchain_service.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/database_service.py
RENAMED
File without changes
|
{request_vm_on_golem-0.1.49 → request_vm_on_golem-0.1.51}/requestor/services/provider_service.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|