hysn-firecracker-python 1.0.3.post0__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.
- firecracker/__init__.py +23 -0
- firecracker/_version.py +34 -0
- firecracker/api.py +183 -0
- firecracker/config.py +30 -0
- firecracker/exceptions.py +33 -0
- firecracker/logger.py +98 -0
- firecracker/microvm.py +1738 -0
- firecracker/network.py +1230 -0
- firecracker/process.py +438 -0
- firecracker/scripts.py +53 -0
- firecracker/utils.py +192 -0
- firecracker/vmm.py +508 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/METADATA +246 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/RECORD +18 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/WHEEL +5 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/entry_points.txt +2 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/licenses/LICENSE +21 -0
- hysn_firecracker_python-1.0.3.post0.dist-info/top_level.txt +1 -0
firecracker/vmm.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
from typing import List, Dict
|
|
5
|
+
from firecracker.logger import Logger
|
|
6
|
+
from firecracker.api import Api
|
|
7
|
+
from firecracker.config import MicroVMConfig
|
|
8
|
+
from firecracker.network import NetworkManager
|
|
9
|
+
from firecracker.process import ProcessManager
|
|
10
|
+
from firecracker.utils import requires_id
|
|
11
|
+
from firecracker.exceptions import VMMError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VMMManager:
|
|
15
|
+
"""Manages Virtual Machine Monitor (VMM) instances.
|
|
16
|
+
|
|
17
|
+
Handles the lifecycle and configuration of Firecracker VMM instances,
|
|
18
|
+
including creation, monitoring, and cleanup of VMM processes.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
logger (Logger): Logger instance for VMM operations
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, verbose: bool = False, level: str = "INFO"):
|
|
25
|
+
self._logger = Logger(level=level, verbose=verbose)
|
|
26
|
+
self._config = MicroVMConfig()
|
|
27
|
+
self._config.verbose = verbose
|
|
28
|
+
self._network = NetworkManager(verbose=verbose, level=level)
|
|
29
|
+
self._process = ProcessManager(verbose=verbose, level=level)
|
|
30
|
+
self._api = None
|
|
31
|
+
|
|
32
|
+
def get_api(self, id: str, timeout: int = 5) -> Api:
|
|
33
|
+
"""Get an API instance for a given VMM ID.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
id (str): VMM ID
|
|
37
|
+
timeout (int): Request timeout in seconds (default: 5)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Api: API client instance
|
|
41
|
+
"""
|
|
42
|
+
socket_file = f"{self._config.data_path}/{id}/firecracker.socket"
|
|
43
|
+
return Api(socket_file, timeout=timeout)
|
|
44
|
+
|
|
45
|
+
def create_vmm_json_file(self, id: str, **kwargs):
|
|
46
|
+
"""Create a JSON file for a VMM.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
id (str): VMM ID
|
|
50
|
+
**kwargs: Keyword arguments for the VMM
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: Path to the created config file
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
VMMError: If file creation fails
|
|
57
|
+
"""
|
|
58
|
+
vm_data = {
|
|
59
|
+
"ID": kwargs.get("ID", id),
|
|
60
|
+
"Name": kwargs.get("Name", ""),
|
|
61
|
+
"CreatedAt": kwargs.get("CreatedAt", ""),
|
|
62
|
+
"Rootfs": kwargs.get("Rootfs", self._config.base_rootfs),
|
|
63
|
+
"Kernel": kwargs.get("Kernel", self._config.kernel_file),
|
|
64
|
+
"State": {
|
|
65
|
+
"Pid": kwargs.get("Pid", ""),
|
|
66
|
+
"Running": kwargs.get("Running", True),
|
|
67
|
+
"Paused": kwargs.get("Paused", False),
|
|
68
|
+
},
|
|
69
|
+
"Network": {f"tap_{id}": {"IPAddress": kwargs.get("IPAddress", "")}},
|
|
70
|
+
"Ports": kwargs.get("Ports", {}),
|
|
71
|
+
"Labels": kwargs.get("Labels", {}),
|
|
72
|
+
"LogPath": kwargs.get("LogPath", f"{self._config.data_path}/{id}/logs"),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
vmm_dir = f"{self._config.data_path}/{id}"
|
|
77
|
+
os.makedirs(vmm_dir, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
file_path = f"{vmm_dir}/config.json"
|
|
80
|
+
with open(file_path, "w") as json_file:
|
|
81
|
+
json.dump(vm_data, json_file, indent=4)
|
|
82
|
+
|
|
83
|
+
if self._config.verbose:
|
|
84
|
+
self._logger.debug(f"Created VMM config file: {file_path}")
|
|
85
|
+
|
|
86
|
+
return file_path
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise VMMError(f"Failed to create VMM config file: {str(e)}") from e
|
|
90
|
+
|
|
91
|
+
def list_vmm(self) -> List[Dict]:
|
|
92
|
+
"""List all VMMs using their config.json files.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List[Dict]: List of dictionaries containing VMM details
|
|
96
|
+
"""
|
|
97
|
+
vmm_list = []
|
|
98
|
+
|
|
99
|
+
running_pids = set(self._process.get_pids())
|
|
100
|
+
has_running_vmms = bool(running_pids)
|
|
101
|
+
|
|
102
|
+
vmm_id_pattern = re.compile(r"^[a-zA-Z0-9]{8}$")
|
|
103
|
+
|
|
104
|
+
data_path = self._config.data_path
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
# Use listdir with error handling
|
|
108
|
+
vmm_dirs = os.listdir(data_path)
|
|
109
|
+
except OSError as e:
|
|
110
|
+
self._logger.error(f"Failed to read data directory {data_path}: {e}")
|
|
111
|
+
return vmm_list
|
|
112
|
+
|
|
113
|
+
for vmm_id in vmm_dirs:
|
|
114
|
+
# Early validation - skip non-matching IDs
|
|
115
|
+
if not vmm_id_pattern.match(vmm_id):
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
vmm_path = os.path.join(data_path, vmm_id)
|
|
119
|
+
|
|
120
|
+
config_path = os.path.join(vmm_path, "config.json")
|
|
121
|
+
if not (os.path.isdir(vmm_path) and os.path.exists(config_path)):
|
|
122
|
+
if has_running_vmms and self._config.verbose:
|
|
123
|
+
self._logger.info(f"Config file not found for VMM ID: {vmm_id}")
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
with open(config_path, "r") as config_file:
|
|
128
|
+
config_data = json.load(config_file)
|
|
129
|
+
|
|
130
|
+
pid = config_data.get("State", {}).get("Pid", "")
|
|
131
|
+
|
|
132
|
+
if pid and pid in running_pids:
|
|
133
|
+
network_key = f"tap_{vmm_id}"
|
|
134
|
+
network_info = config_data.get("Network", {}).get(network_key, {})
|
|
135
|
+
ports_info = config_data.get("Ports", {})
|
|
136
|
+
|
|
137
|
+
vmm_info = {
|
|
138
|
+
"id": config_data.get("ID", vmm_id),
|
|
139
|
+
"name": config_data.get("Name", ""),
|
|
140
|
+
"pid": pid,
|
|
141
|
+
"ip_addr": network_info.get("IPAddress", ""),
|
|
142
|
+
"state": "Running"
|
|
143
|
+
if config_data.get("State", {}).get("Running", False)
|
|
144
|
+
else "Paused",
|
|
145
|
+
"created_at": config_data.get("CreatedAt", ""),
|
|
146
|
+
"ports": ports_info,
|
|
147
|
+
"labels": config_data.get("Labels", {}),
|
|
148
|
+
}
|
|
149
|
+
vmm_list.append(vmm_info)
|
|
150
|
+
|
|
151
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
152
|
+
if self._config.verbose:
|
|
153
|
+
self._logger.warn(f"Failed to read config for VMM {vmm_id}: {e}")
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
return vmm_list
|
|
157
|
+
|
|
158
|
+
def find_vmm_by_id(self, id: str) -> str:
|
|
159
|
+
"""Find a VMM by ID and return its ID.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
id (str): ID of the VMM to find
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
str: ID of the found VMM or error message
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
vmm_list = self.list_vmm()
|
|
169
|
+
for vmm_info in vmm_list:
|
|
170
|
+
if vmm_info["id"] == id:
|
|
171
|
+
return vmm_info["id"]
|
|
172
|
+
|
|
173
|
+
return f"VMM with ID {id} not found"
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise VMMError(f"Error finding VMM by ID: {str(e)}")
|
|
177
|
+
|
|
178
|
+
def find_vmm_by_labels(self, state: str, labels: Dict[str, str]) -> List[str]:
|
|
179
|
+
"""Find VMMs by state (Running or Paused) and multiple labels, and return their IDs.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
state (str): State to filter by ('Running' or 'Paused')
|
|
183
|
+
labels (Dict[str, str]): Dictionary of labels to search for
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
List[str]: List of VMM IDs that match the state and all the labels
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
matching_vmm_ids = []
|
|
190
|
+
|
|
191
|
+
vmm_list = self.list_vmm()
|
|
192
|
+
|
|
193
|
+
if not vmm_list:
|
|
194
|
+
return matching_vmm_ids
|
|
195
|
+
|
|
196
|
+
state_matching_vmms = [
|
|
197
|
+
vmm_info for vmm_info in vmm_list if vmm_info["state"] == state
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
if not state_matching_vmms:
|
|
201
|
+
return matching_vmm_ids
|
|
202
|
+
|
|
203
|
+
for vmm_info in state_matching_vmms:
|
|
204
|
+
vmm_id = vmm_info["id"]
|
|
205
|
+
config_path = os.path.join(
|
|
206
|
+
self._config.data_path, vmm_id, "config.json"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if not os.path.exists(config_path):
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
with open(config_path, "r") as config_file:
|
|
214
|
+
config_data = json.load(config_file)
|
|
215
|
+
|
|
216
|
+
vmm_labels = config_data.get("Labels", {})
|
|
217
|
+
if all(
|
|
218
|
+
vmm_labels.get(key) == value for key, value in labels.items()
|
|
219
|
+
):
|
|
220
|
+
vmm_info = {
|
|
221
|
+
"id": config_data.get("ID", vmm_id),
|
|
222
|
+
"name": config_data.get("Name", ""),
|
|
223
|
+
"state": "Running"
|
|
224
|
+
if config_data.get("State", {}).get("Running", False)
|
|
225
|
+
else "Paused",
|
|
226
|
+
"created_at": config_data.get("CreatedAt", ""),
|
|
227
|
+
}
|
|
228
|
+
matching_vmm_ids.append(vmm_info)
|
|
229
|
+
|
|
230
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
231
|
+
if self._config.verbose:
|
|
232
|
+
self._logger.warn(
|
|
233
|
+
f"Failed to read config for VMM {vmm_id}: {e}"
|
|
234
|
+
)
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
return matching_vmm_ids
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
raise VMMError(f"Error finding VMM by labels: {str(e)}")
|
|
241
|
+
|
|
242
|
+
def update_vmm_state(self, id: str, state: str) -> str:
|
|
243
|
+
"""Update VM state (pause/resume).
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
state (str): Target state ("Paused" or "Resumed")
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
str: Status message
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
api = self.get_api(id)
|
|
253
|
+
response = api.vm.patch(state=state)
|
|
254
|
+
|
|
255
|
+
if self._config.verbose:
|
|
256
|
+
self._logger.debug(f"Changed VMM {id} state response: {response}")
|
|
257
|
+
|
|
258
|
+
return f"{state} VMM {id} successfully"
|
|
259
|
+
|
|
260
|
+
except Exception as e:
|
|
261
|
+
raise VMMError(f"Failed to {state.lower()} VMM {id}: {str(e)}")
|
|
262
|
+
|
|
263
|
+
finally:
|
|
264
|
+
if api:
|
|
265
|
+
api.close()
|
|
266
|
+
|
|
267
|
+
@requires_id
|
|
268
|
+
def get_vmm_config(self, id: str) -> Dict:
|
|
269
|
+
"""Get the configuration for a specific VMM.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
id (str): ID of the VMM to query
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
dict: VMM configuration
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
RuntimeError: If VMM ID is invalid or VMM is not running
|
|
279
|
+
"""
|
|
280
|
+
try:
|
|
281
|
+
api = self.get_api(id)
|
|
282
|
+
response = api.vm_config.get().json()
|
|
283
|
+
|
|
284
|
+
if self._config.verbose:
|
|
285
|
+
self._logger.debug(f"VMM {id} configuration response: {response}")
|
|
286
|
+
|
|
287
|
+
return response
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
raise VMMError(f"Failed to get VMM configuration: {str(e)}")
|
|
291
|
+
|
|
292
|
+
finally:
|
|
293
|
+
api.close()
|
|
294
|
+
|
|
295
|
+
def get_vmm_state(self, id: str) -> str:
|
|
296
|
+
"""Get the state of a specific VMM.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
id (str): ID of the VMM to query
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
str: VMM state ('Running', 'Paused', 'Unknown', etc.)
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
VMMError: If VMM state cannot be retrieved
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
api = self.get_api(id)
|
|
309
|
+
response = api.describe.get().json()
|
|
310
|
+
state = response.get("state")
|
|
311
|
+
|
|
312
|
+
if isinstance(state, str) and state.strip():
|
|
313
|
+
return state
|
|
314
|
+
|
|
315
|
+
return "Unknown"
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
raise VMMError(f"Failed to get state for VMM {id}: {str(e)}")
|
|
319
|
+
|
|
320
|
+
finally:
|
|
321
|
+
if api:
|
|
322
|
+
api.close()
|
|
323
|
+
|
|
324
|
+
def get_vmm_ip_addr(self, id: str) -> str:
|
|
325
|
+
"""Get the IP address of a specific VMM.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
id (str): ID of the VMM to query
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
str: IP address of the VMM
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
VMMError: If no IP address is found or an error occurs after
|
|
335
|
+
retries
|
|
336
|
+
"""
|
|
337
|
+
try:
|
|
338
|
+
api = self.get_api(id)
|
|
339
|
+
vmm_config = api.vm_config.get().json()
|
|
340
|
+
boot_args = vmm_config.get("boot-source", {}).get("boot_args", "")
|
|
341
|
+
|
|
342
|
+
ip_match = re.search(r"ip=([0-9.]+)", boot_args)
|
|
343
|
+
if ip_match:
|
|
344
|
+
ip_addr = ip_match.group(1)
|
|
345
|
+
return ip_addr
|
|
346
|
+
|
|
347
|
+
else:
|
|
348
|
+
if self._config.verbose:
|
|
349
|
+
self._logger.info(f"No ip= found in boot-args for VMM {id}")
|
|
350
|
+
return "Unknown"
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
raise VMMError(f"Error while retrieving IP address for VMM {id}: {str(e)}")
|
|
354
|
+
|
|
355
|
+
finally:
|
|
356
|
+
api.close()
|
|
357
|
+
|
|
358
|
+
def check_network_overlap(self, ip_addr: str) -> bool:
|
|
359
|
+
"""Check if the network configuration overlaps with another VMM.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
ip_addr (str): IP address to check for overlap
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
bool: True if the IP address overlaps, False otherwise.
|
|
366
|
+
"""
|
|
367
|
+
try:
|
|
368
|
+
vmm_list = self.list_vmm()
|
|
369
|
+
|
|
370
|
+
existing_ips = {
|
|
371
|
+
vmm_info["ip_addr"]
|
|
372
|
+
for vmm_info in vmm_list
|
|
373
|
+
if vmm_info.get("ip_addr") and vmm_info["ip_addr"] != "Unknown"
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return ip_addr in existing_ips
|
|
377
|
+
|
|
378
|
+
except Exception as e:
|
|
379
|
+
raise VMMError(f"Error checking network overlap: {str(e)}")
|
|
380
|
+
|
|
381
|
+
def create_vmm_dir(self, path: str):
|
|
382
|
+
"""Create directories for the microVM.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
path (str): Path to the VMM directory to create
|
|
386
|
+
"""
|
|
387
|
+
try:
|
|
388
|
+
if not os.path.exists(path):
|
|
389
|
+
os.makedirs(path, exist_ok=True)
|
|
390
|
+
if self._config.verbose:
|
|
391
|
+
self._logger.info(f"Directory {path} is created")
|
|
392
|
+
|
|
393
|
+
except Exception as e:
|
|
394
|
+
raise VMMError(f"Failed to create directory at {path}: {str(e)}")
|
|
395
|
+
|
|
396
|
+
def create_log_file(self, id: str, log_file: str):
|
|
397
|
+
"""Create a log file for the microVM.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
log_file (str): Name of the log file to create
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
log_dir = f"{self._config.data_path}/{id}/logs"
|
|
404
|
+
|
|
405
|
+
if not os.path.exists(f"{log_dir}/{log_file}"):
|
|
406
|
+
with open(f"{log_dir}/{log_file}", "w"):
|
|
407
|
+
pass
|
|
408
|
+
if self._config.verbose:
|
|
409
|
+
self._logger.info(f"Log file {log_dir}/{log_file} is created")
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
raise VMMError(f"Unable to create log file at {log_dir}: {str(e)}")
|
|
413
|
+
|
|
414
|
+
def delete_vmm_dir(self, id: str = None):
|
|
415
|
+
"""
|
|
416
|
+
Clean up all resources associated with the microVM by removing the
|
|
417
|
+
VMM directory.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
id (str): ID of the VMM to delete
|
|
421
|
+
"""
|
|
422
|
+
import shutil
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
vmm_dir = f"{self._config.data_path}/{id}"
|
|
426
|
+
|
|
427
|
+
if os.path.exists(vmm_dir):
|
|
428
|
+
shutil.rmtree(vmm_dir)
|
|
429
|
+
if self._config.verbose:
|
|
430
|
+
self._logger.info(f"Directory {vmm_dir} is removed")
|
|
431
|
+
|
|
432
|
+
except Exception as e:
|
|
433
|
+
self._logger.error(f"Failed to remove {vmm_dir} directory: {str(e)}")
|
|
434
|
+
raise VMMError(f"Failed to remove {vmm_dir} directory: {str(e)}")
|
|
435
|
+
|
|
436
|
+
def delete_vmm(self, id: str = None) -> str:
|
|
437
|
+
"""Delete VMM instances from the config.json file.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
id (str, optional): ID of specific VMM to delete. If None, deletes all VMMs.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
str: Status message indicating deletion results
|
|
444
|
+
"""
|
|
445
|
+
try:
|
|
446
|
+
vmm_list = self.list_vmm()
|
|
447
|
+
|
|
448
|
+
if not vmm_list:
|
|
449
|
+
return "No VMMs found to delete"
|
|
450
|
+
|
|
451
|
+
if id:
|
|
452
|
+
if not any(vmm["id"] == id for vmm in vmm_list):
|
|
453
|
+
return f"VMM with ID {id} not found"
|
|
454
|
+
ids_to_delete = [id]
|
|
455
|
+
else:
|
|
456
|
+
ids_to_delete = [vmm["id"] for vmm in vmm_list]
|
|
457
|
+
|
|
458
|
+
deleted_count = 0
|
|
459
|
+
for vmm_id in ids_to_delete:
|
|
460
|
+
try:
|
|
461
|
+
self.cleanup(vmm_id)
|
|
462
|
+
deleted_count += 1
|
|
463
|
+
if self._config.verbose:
|
|
464
|
+
self._logger.info(f"Removed VMM {vmm_id}")
|
|
465
|
+
except Exception as e:
|
|
466
|
+
self._logger.error(f"Failed to delete VMM {vmm_id}: {e}")
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
if id:
|
|
470
|
+
return f"VMM {id} {'removed' if deleted_count > 0 else 'not found'}"
|
|
471
|
+
else:
|
|
472
|
+
return f"Deleted {deleted_count} VMM(s)"
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
raise VMMError(f"Error during VMM deletion: {str(e)}")
|
|
476
|
+
|
|
477
|
+
def cleanup(self, id=None):
|
|
478
|
+
"""Clean up network and process resources for a VMM."""
|
|
479
|
+
try:
|
|
480
|
+
self._process.stop(id)
|
|
481
|
+
self._network.cleanup(f"tap_{id}")
|
|
482
|
+
self.delete_vmm_dir(id)
|
|
483
|
+
|
|
484
|
+
except Exception as e:
|
|
485
|
+
raise VMMError(f"Failed to cleanup VMM {id}: {str(e)}") from e
|
|
486
|
+
|
|
487
|
+
def socket_file(self, id: str) -> str:
|
|
488
|
+
"""Ensure the socket file is ready for use, unlinking if necessary.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
str: Path to the socket file
|
|
492
|
+
|
|
493
|
+
Raises:
|
|
494
|
+
VMMError: If unable to create or verify the socket file
|
|
495
|
+
"""
|
|
496
|
+
try:
|
|
497
|
+
socket_file = f"{self._config.data_path}/{id}/firecracker.socket"
|
|
498
|
+
|
|
499
|
+
if os.path.exists(socket_file):
|
|
500
|
+
os.unlink(socket_file)
|
|
501
|
+
if self._config.verbose:
|
|
502
|
+
self._logger.info(f"Unlinked existing socket file {socket_file}")
|
|
503
|
+
|
|
504
|
+
self.create_vmm_dir(f"{self._config.data_path}/{id}")
|
|
505
|
+
return socket_file
|
|
506
|
+
|
|
507
|
+
except OSError as e:
|
|
508
|
+
raise VMMError(f"Failed to ensure socket file {socket_file}: {e}")
|