golem-vm-provider 0.1.2__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: golem-vm-provider
3
- Version: 0.1.2
3
+ Version: 0.1.7
4
4
  Summary: VM on Golem Provider Node - Run your own provider node to offer VMs on the Golem Network
5
- Home-page: https://github.com/cryptobench/vm-on-golem
6
5
  Keywords: golem,vm,provider,cloud,decentralized
7
6
  Author: Phillip Jensen
8
7
  Author-email: phillip+vm-on-golem@golemgrid.com
@@ -33,6 +32,7 @@ Requires-Dist: requests (>=2.31.0,<3.0.0)
33
32
  Requires-Dist: rich (>=13.7.0,<14.0.0)
34
33
  Requires-Dist: setuptools (>=69.0.3,<70.0.0)
35
34
  Requires-Dist: uvicorn (>=0.15.0,<0.16.0)
35
+ Project-URL: Homepage, https://github.com/cryptobench/vm-on-golem
36
36
  Project-URL: Repository, https://github.com/cryptobench/vm-on-golem
37
37
  Description-Content-Type: text/markdown
38
38
 
@@ -1,7 +1,7 @@
1
1
  provider/__init__.py,sha256=HO1fkPpZqPO3z8O8-eVIyx8xXSMIVuTR_b1YF0RtXOg,45
2
2
  provider/api/__init__.py,sha256=ssX1ugDqEPt8Fn04IymgmG-Ev8PiXLsCSaiZVvHQnec,344
3
3
  provider/api/models.py,sha256=JOzoNf1oE5N97UqTN5xuIrTkqn2tCHqPDaIzGA3jUyo,3513
4
- provider/api/routes.py,sha256=KvTbFk61Vo1_4PeLzV0ohMXyuRidt1SJRLmpwvufJtQ,6486
4
+ provider/api/routes.py,sha256=P27RQvNqFWn6PacRwr1PaVz-yv5KAWsp9KeORejkXSI,6452
5
5
  provider/config.py,sha256=Q6xmU6cPRBUYkVusv-Fc1U4ekr4OYLXfOfEtaWL9miM,5000
6
6
  provider/discovery/__init__.py,sha256=VR3NRoQtZRH5Vs8FG7jnGLR7p7wn7XeZdLaBb3t8e1g,123
7
7
  provider/discovery/advertiser.py,sha256=yv7RbRf1K43qOLAEa2Olj9hhN8etl2qsBuoHok0xoVs,6784
@@ -16,11 +16,11 @@ provider/utils/retry.py,sha256=ekP2ucaSJNN-lBcrIvyHa4QYPKNITMl1a5V1X6BBvsw,1560
16
16
  provider/vm/__init__.py,sha256=JGs50tUmzOR1rQ_w4fMY_3XWylmiA1G7KKWZkVw51mY,501
17
17
  provider/vm/cloud_init.py,sha256=o2CWLjl1ZN9fSEFHAWO-glh7BW-DxAMSe0MbqhzKNTg,1709
18
18
  provider/vm/models.py,sha256=zkfvP5Z50SPDNajwZTt9NTDIMRQIsZLvSOsuirHEcJM,6256
19
- provider/vm/multipass.py,sha256=kS0HkKZT6--YEjxxdr4Ot0oF24T128hLrq-STKsuCK4,16406
19
+ provider/vm/multipass.py,sha256=RLUqCeoYz4PG8RL7dBu_TzjNEAmgIz9NonBtSuYc4kw,16431
20
20
  provider/vm/name_mapper.py,sha256=MrshNeJ4Dw-WBsyiIVcn9N5xyOxaBKX4Yqhyh_m5IFg,4103
21
- provider/vm/port_manager.py,sha256=e9qQCNSQX5KcY4zxnkCRBRvzXq4hjcY4nuQJ8yygsag,8423
22
- provider/vm/proxy_manager.py,sha256=nLcs9Nane3ANvfgvHL4ZPytTuHCqG1ArQ-_ZSyMaDyQ,10188
23
- golem_vm_provider-0.1.2.dist-info/METADATA,sha256=Gw7qPoo4J1aV2EhdhSG-GkmlkJdhwancofLL7ubF9yA,10449
24
- golem_vm_provider-0.1.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
25
- golem_vm_provider-0.1.2.dist-info/entry_points.txt,sha256=E4rCWo_Do_2zCG_GewNuftfVlHF_8b_OvioZre0dfeA,54
26
- golem_vm_provider-0.1.2.dist-info/RECORD,,
21
+ provider/vm/port_manager.py,sha256=147phrKFXiv1jpJfqpwHsuHqYTj4t2kP3_UV0lV8Lr0,10063
22
+ provider/vm/proxy_manager.py,sha256=cu0FPPbeCc3CR6NRE_CnLjiRg7xVdSFUylVUOL1g1sI,10154
23
+ golem_vm_provider-0.1.7.dist-info/METADATA,sha256=LsfKA092K5_R6bZA25J5E3S3JLzEK-WTY2IJSBv7k4I,10461
24
+ golem_vm_provider-0.1.7.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
25
+ golem_vm_provider-0.1.7.dist-info/entry_points.txt,sha256=E4rCWo_Do_2zCG_GewNuftfVlHF_8b_OvioZre0dfeA,54
26
+ golem_vm_provider-0.1.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
provider/api/routes.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  from typing import List
3
3
  from pathlib import Path
4
- from fastapi import APIRouter, HTTPException
4
+ from fastapi import APIRouter, HTTPException, Request
5
5
 
6
6
  from ..config import settings
7
7
  from ..utils.logging import setup_logger, PROCESS, SUCCESS
@@ -9,19 +9,12 @@ from ..utils.ascii_art import vm_creation_animation, vm_status_change
9
9
  from ..vm.models import VMInfo, VMStatus, VMAccessInfo, VMConfig, VMResources
10
10
  from .models import CreateVMRequest
11
11
  from ..vm.multipass import MultipassProvider, MultipassError
12
- from ..discovery.resource_tracker import ResourceTracker
13
- from ..vm.port_manager import PortManager
14
12
 
15
13
  logger = setup_logger(__name__)
16
14
  router = APIRouter()
17
15
 
18
- # Initialize components
19
- resource_tracker = ResourceTracker()
20
- port_manager = PortManager()
21
- provider = MultipassProvider(resource_tracker, port_manager)
22
-
23
16
  @router.post("/vms", response_model=VMInfo)
24
- async def create_vm(request: CreateVMRequest) -> VMInfo:
17
+ async def create_vm(request: CreateVMRequest, req: Request) -> VMInfo:
25
18
  """Create a new VM."""
26
19
  try:
27
20
  logger.info(f"📥 Received VM creation request for '{request.name}'")
@@ -47,7 +40,7 @@ async def create_vm(request: CreateVMRequest) -> VMInfo:
47
40
 
48
41
  # Check and allocate resources
49
42
  logger.process("🔄 Allocating resources")
50
- if not await resource_tracker.allocate(resources):
43
+ if not await req.app.state.resource_tracker.allocate(resources):
51
44
  logger.error("❌ Insufficient resources available")
52
45
  raise HTTPException(400, "Insufficient resources available on provider")
53
46
 
@@ -62,7 +55,7 @@ async def create_vm(request: CreateVMRequest) -> VMInfo:
62
55
 
63
56
  # Create VM
64
57
  logger.process(f"🔄 Creating VM with config: {config}")
65
- vm_info = await provider.create_vm(config)
58
+ vm_info = await req.app.state.provider.create_vm(config)
66
59
 
67
60
  # Show success message
68
61
  await vm_creation_animation(request.name)
@@ -70,7 +63,7 @@ async def create_vm(request: CreateVMRequest) -> VMInfo:
70
63
  except Exception as e:
71
64
  # If VM creation fails, deallocate resources
72
65
  logger.warning("⚠️ VM creation failed, deallocating resources")
73
- await resource_tracker.deallocate(resources)
66
+ await req.app.state.resource_tracker.deallocate(resources)
74
67
  raise
75
68
 
76
69
  except MultipassError as e:
@@ -78,13 +71,13 @@ async def create_vm(request: CreateVMRequest) -> VMInfo:
78
71
  raise HTTPException(500, str(e))
79
72
 
80
73
  @router.get("/vms", response_model=List[VMInfo])
81
- async def list_vms() -> List[VMInfo]:
74
+ async def list_vms(req: Request) -> List[VMInfo]:
82
75
  """List all VMs."""
83
76
  try:
84
77
  logger.info("📋 Listing all VMs")
85
78
  vms = []
86
- for vm_id in resource_tracker.get_allocated_vms():
87
- vm_info = await provider.get_vm_status(vm_id)
79
+ for vm_id in req.app.state.resource_tracker.get_allocated_vms():
80
+ vm_info = await req.app.state.provider.get_vm_status(vm_id)
88
81
  vms.append(vm_info)
89
82
  return vms
90
83
  except MultipassError as e:
@@ -92,11 +85,11 @@ async def list_vms() -> List[VMInfo]:
92
85
  raise HTTPException(500, str(e))
93
86
 
94
87
  @router.get("/vms/{requestor_name}", response_model=VMInfo)
95
- async def get_vm_status(requestor_name: str) -> VMInfo:
88
+ async def get_vm_status(requestor_name: str, req: Request) -> VMInfo:
96
89
  """Get VM status."""
97
90
  try:
98
91
  logger.info(f"🔍 Getting status for VM '{requestor_name}'")
99
- status = await provider.get_vm_status(requestor_name)
92
+ status = await req.app.state.provider.get_vm_status(requestor_name)
100
93
  vm_status_change(requestor_name, status.status.value)
101
94
  return status
102
95
  except MultipassError as e:
@@ -104,16 +97,16 @@ async def get_vm_status(requestor_name: str) -> VMInfo:
104
97
  raise HTTPException(500, str(e))
105
98
 
106
99
  @router.get("/vms/{requestor_name}/access", response_model=VMAccessInfo)
107
- async def get_vm_access(requestor_name: str) -> VMAccessInfo:
100
+ async def get_vm_access(requestor_name: str, req: Request) -> VMAccessInfo:
108
101
  """Get VM access information."""
109
102
  try:
110
103
  # Get VM info
111
- vm = await provider.get_vm_status(requestor_name)
104
+ vm = await req.app.state.provider.get_vm_status(requestor_name)
112
105
  if not vm:
113
106
  raise HTTPException(404, "VM not found")
114
107
 
115
108
  # Get multipass name from mapper
116
- multipass_name = await provider.name_mapper.get_multipass_name(requestor_name)
109
+ multipass_name = await req.app.state.provider.name_mapper.get_multipass_name(requestor_name)
117
110
  if not multipass_name:
118
111
  raise HTTPException(404, "VM mapping not found")
119
112
 
@@ -130,7 +123,7 @@ async def get_vm_access(requestor_name: str) -> VMAccessInfo:
130
123
  raise HTTPException(500, str(e))
131
124
 
132
125
  @router.delete("/vms/{requestor_name}")
133
- async def delete_vm(requestor_name: str) -> None:
126
+ async def delete_vm(requestor_name: str, req: Request) -> None:
134
127
  """Delete a VM.
135
128
 
136
129
  Args:
@@ -140,14 +133,14 @@ async def delete_vm(requestor_name: str) -> None:
140
133
  logger.process(f"🗑️ Deleting VM '{requestor_name}'")
141
134
 
142
135
  # Get multipass name from mapper
143
- multipass_name = await provider.name_mapper.get_multipass_name(requestor_name)
136
+ multipass_name = await req.app.state.provider.name_mapper.get_multipass_name(requestor_name)
144
137
  if not multipass_name:
145
138
  logger.warning(f"No multipass name found for VM '{requestor_name}' (may have been already deleted)")
146
139
  return
147
140
 
148
141
  try:
149
142
  vm_status_change(requestor_name, "STOPPING", "Cleanup in progress")
150
- await provider.delete_vm(requestor_name)
143
+ await req.app.state.provider.delete_vm(requestor_name)
151
144
  vm_status_change(requestor_name, "TERMINATED", "Cleanup complete")
152
145
  logger.success(f"✨ Successfully deleted VM '{requestor_name}'")
153
146
  except MultipassError as e:
provider/vm/multipass.py CHANGED
@@ -37,7 +37,7 @@ class MultipassProvider(VMProvider):
37
37
  self.vm_data_dir.mkdir(parents=True, exist_ok=True)
38
38
 
39
39
  # Initialize managers
40
- self.proxy_manager = PythonProxyManager()
40
+ self.proxy_manager = PythonProxyManager(port_manager=port_manager)
41
41
  self.name_mapper = VMNameMapper(self.vm_data_dir / "vm_names.json")
42
42
 
43
43
  def _verify_installation(self) -> None:
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import json
3
+ import socket
3
4
  import logging
4
5
  import asyncio
5
6
  from pathlib import Path
@@ -172,7 +173,23 @@ class PortManager:
172
173
  if vm_id in self._used_ports:
173
174
  port = self._used_ports[vm_id]
174
175
  if port in self.verified_ports:
175
- return port
176
+ # Quick check if port is still available
177
+ try:
178
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
179
+ sock.settimeout(1)
180
+ result = sock.connect_ex(('127.0.0.1', port))
181
+ sock.close()
182
+
183
+ if result != 0: # Port is available
184
+ return port
185
+ else:
186
+ # Port is in use, remove from verified ports
187
+ self.verified_ports.remove(port)
188
+ self._used_ports.pop(vm_id)
189
+ except Exception as e:
190
+ logger.debug(f"Failed to check port {port}: {e}")
191
+ # Keep the port if check fails, let proxy setup handle any issues
192
+ return port
176
193
  else:
177
194
  # Previously allocated port is no longer verified
178
195
  self._used_ports.pop(vm_id)
@@ -182,10 +199,24 @@ class PortManager:
182
199
  # Find first available verified port
183
200
  for port in sorted(self.verified_ports):
184
201
  if port not in used_ports:
185
- self._used_ports[vm_id] = port
186
- self._save_state()
187
- logger.info(f"Allocated verified port {port} for VM {vm_id}")
188
- return port
202
+ # Quick check if port is actually available
203
+ try:
204
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
205
+ sock.settimeout(1)
206
+ result = sock.connect_ex(('127.0.0.1', port))
207
+ sock.close()
208
+
209
+ if result != 0: # Port is available
210
+ self._used_ports[vm_id] = port
211
+ self._save_state()
212
+ logger.info(f"Allocated verified port {port} for VM {vm_id}")
213
+ return port
214
+ else:
215
+ # Port is in use, remove from verified ports
216
+ self.verified_ports.remove(port)
217
+ except Exception as e:
218
+ logger.debug(f"Failed to check port {port}: {e}")
219
+ continue
189
220
 
190
221
  logger.error("No verified ports available for allocation")
191
222
  return None
@@ -146,7 +146,7 @@ class PythonProxyManager:
146
146
 
147
147
  def __init__(
148
148
  self,
149
- port_manager: Optional[PortManager] = None,
149
+ port_manager: PortManager,
150
150
  state_file: Optional[str] = None
151
151
  ):
152
152
  """Initialize the proxy manager.
@@ -155,7 +155,7 @@ class PythonProxyManager:
155
155
  port_manager: Port allocation manager
156
156
  state_file: Path to persist proxy state
157
157
  """
158
- self.port_manager = port_manager or PortManager()
158
+ self.port_manager = port_manager
159
159
  self.state_file = state_file or os.path.expanduser("~/.golem/provider/proxy_state.json")
160
160
  self._proxies: Dict[str, ProxyServer] = {} # vm_id -> ProxyServer
161
161
  self._load_state()