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.
@@ -0,0 +1,23 @@
1
+ from .microvm import MicroVM
2
+ from .api import Api
3
+ from .logger import Logger
4
+ from . import scripts
5
+
6
+ try:
7
+ scripts.check_firecracker_binary()
8
+ scripts.create_firecracker_directory()
9
+ except scripts.ConfigurationError as e:
10
+ print(f"Warning: {e}")
11
+
12
+ try:
13
+ from ._version import version as __version__
14
+ except ImportError:
15
+ __version__ = "0.0.0"
16
+
17
+ __all__ = [
18
+ "MicroVM",
19
+ "Api",
20
+ "Logger",
21
+ "scripts",
22
+ "__version__",
23
+ ]
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '1.0.3.post0'
32
+ __version_tuple__ = version_tuple = (1, 0, 3, 'post0')
33
+
34
+ __commit_id__ = commit_id = 'g79b65ab4e'
firecracker/api.py ADDED
@@ -0,0 +1,183 @@
1
+ import requests
2
+ import urllib.parse
3
+ from http import HTTPStatus
4
+ from .exceptions import APIError
5
+ from requests_unixsocket import UnixAdapter
6
+
7
+ DEFAULT_SCHEME = "http://"
8
+ DEFAULT_TIMEOUT = 5
9
+
10
+
11
+ class Session(requests.Session):
12
+ """An HTTP over UNIX sockets Session with optimized connection pooling"""
13
+
14
+ def __init__(self, timeout=DEFAULT_TIMEOUT):
15
+ """Create a Session object.
16
+
17
+ Args:
18
+ timeout (int): Request timeout in seconds
19
+ """
20
+ super().__init__()
21
+ adapter = UnixAdapter(
22
+ pool_connections=20, pool_maxsize=20, max_retries=3, pool_block=True
23
+ )
24
+ self.mount(DEFAULT_SCHEME, adapter)
25
+
26
+
27
+ class Resource:
28
+ """An abstraction over a REST path"""
29
+
30
+ def __init__(self, api, resource, id_field=None):
31
+ """Initialize a REST resource.
32
+
33
+ Args:
34
+ api (Api): The API client instance
35
+ resource (str): The resource path
36
+ id_field (str, optional): The field name used for resource ID
37
+ """
38
+ self._api = api
39
+ self.resource = resource
40
+ self.id_field = id_field
41
+
42
+ def get(self, timeout=None):
43
+ """Make a GET request.
44
+
45
+ Args:
46
+ timeout (int, optional): Request timeout in seconds
47
+
48
+ Returns:
49
+ requests.Response: The HTTP response
50
+
51
+ Raises:
52
+ APIError: If request fails or returns an error response
53
+ """
54
+ try:
55
+ url = self._api.endpoint + self.resource
56
+ request_timeout = (
57
+ timeout if timeout is not None else self._api.get_timeout()
58
+ )
59
+ with self._api.session.get(url, timeout=request_timeout) as res:
60
+ if res.status_code != HTTPStatus.OK:
61
+ json = res.json()
62
+ if "fault_message" in json:
63
+ raise APIError(f"API fault: {json['fault_message']}")
64
+ elif "error" in json:
65
+ raise APIError(f"API error: {json['error']}")
66
+ raise APIError(f"Unexpected response: {res.content}")
67
+
68
+ return res
69
+
70
+ except requests.RequestException as e:
71
+ raise APIError(f"GET request failed: {str(e)}") from e
72
+ except ValueError as e:
73
+ raise APIError(f"Invalid JSON response: {str(e)}") from e
74
+
75
+ def put(self, **kwargs):
76
+ """Make a PUT request.
77
+
78
+ Args:
79
+ **kwargs: Key-value pairs for the request body
80
+
81
+ Returns:
82
+ requests.Response: The HTTP response
83
+ """
84
+ path = self.resource
85
+ if self.id_field is not None:
86
+ path += "/" + kwargs[self.id_field]
87
+ return self.request("PUT", path, **kwargs)
88
+
89
+ def patch(self, **kwargs):
90
+ """Make a PATCH request.
91
+
92
+ Args:
93
+ **kwargs: Key-value pairs for the request body
94
+
95
+ Returns:
96
+ requests.Response: The HTTP response
97
+ """
98
+ path = self.resource
99
+ if self.id_field is not None:
100
+ path += "/" + kwargs[self.id_field]
101
+ return self.request("PATCH", path, **kwargs)
102
+
103
+ def request(self, method, path, timeout=None, **kwargs):
104
+ """Make an HTTP request to the Firecracker API.
105
+
106
+ Args:
107
+ method (str): HTTP method (GET, PUT, POST, DELETE, etc.)
108
+ path (str): API endpoint path
109
+ timeout (int, optional): Request timeout in seconds
110
+ **kwargs: Additional arguments to be sent as JSON in request body
111
+
112
+ Returns:
113
+ requests.Response: The HTTP response from the API
114
+
115
+ Raises:
116
+ APIError: If request fails or returns an error response
117
+ """
118
+ try:
119
+ kwargs = {key: val for key, val in kwargs.items() if val is not None}
120
+ url = self._api.endpoint + path
121
+ request_timeout = (
122
+ timeout if timeout is not None else self._api.get_timeout()
123
+ )
124
+ with self._api.session.request(
125
+ method, url, json=kwargs, timeout=request_timeout
126
+ ) as res:
127
+ if res.status_code != HTTPStatus.NO_CONTENT:
128
+ json = res.json()
129
+ if "fault_message" in json:
130
+ raise APIError(f"API fault: {json['fault_message']}")
131
+ elif "error" in json:
132
+ raise APIError(f"API error: {json['error']}")
133
+ raise APIError(f"Unexpected response: {res.content}")
134
+ return res
135
+ except requests.RequestException as e:
136
+ raise APIError(f"Request failed: {str(e)}") from e
137
+ except ValueError as e:
138
+ raise APIError(f"Invalid JSON response: {str(e)}") from e
139
+
140
+
141
+ class Api:
142
+ """A simple HTTP client for Firecracker API"""
143
+
144
+ def __init__(self, socket_file, timeout=DEFAULT_TIMEOUT):
145
+ """Initialize API client.
146
+
147
+ Args:
148
+ socket_file (str): Path to Firecracker API socket
149
+ timeout (int): Request timeout in seconds (default: 5)
150
+ """
151
+ self.socket = socket_file
152
+ self.timeout = timeout
153
+ url_encoded_path = urllib.parse.quote_plus(socket_file)
154
+ self.endpoint = DEFAULT_SCHEME + url_encoded_path
155
+ self.session = Session(timeout=timeout)
156
+
157
+ self.describe = Resource(self, "/")
158
+ self.vm = Resource(self, "/vm")
159
+ self.vm_config = Resource(self, "/vm/config")
160
+ self.actions = Resource(self, "/actions")
161
+ self.boot = Resource(self, "/boot-source")
162
+ self.drive = Resource(self, "/drives", "drive_id")
163
+ self.version = Resource(self, "/version")
164
+ self.logger = Resource(self, "/logger")
165
+ self.machine_config = Resource(self, "/machine-config")
166
+ self.network = Resource(self, "/network-interfaces", "iface_id")
167
+ self.mmds = Resource(self, "/mmds")
168
+ self.mmds_config = Resource(self, "/mmds/config")
169
+ self.create_snapshot = Resource(self, "/snapshot/create")
170
+ self.load_snapshot = Resource(self, "/snapshot/load")
171
+ self.vsock = Resource(self, "/vsock")
172
+
173
+ def get_timeout(self):
174
+ """Get timeout for API requests.
175
+
176
+ Returns:
177
+ int: Timeout value in seconds
178
+ """
179
+ return getattr(self, "timeout", DEFAULT_TIMEOUT)
180
+
181
+ def close(self):
182
+ """Close the session to release resources."""
183
+ self.session.close()
firecracker/config.py ADDED
@@ -0,0 +1,30 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+
4
+
5
+ @dataclass
6
+ class MicroVMConfig:
7
+ """Configuration defaults for Firecracker microVMs."""
8
+ data_path: str = "/var/lib/firecracker"
9
+ binary_path: str = "/usr/local/bin/firecracker"
10
+ snapshot_path: str = "/var/lib/firecracker/snapshots"
11
+ kernel_file: str = None
12
+ rootfs_size: str = "5G"
13
+ initrd_file: str = None
14
+ init_file: str = "/sbin/init"
15
+ base_rootfs: str = None
16
+ overlayfs: bool = False
17
+ overlayfs_file: str = None
18
+ ip_addr: str = "172.16.0.2"
19
+ mmds_enabled: bool = False
20
+ mmds_ip: str = "169.254.169.254"
21
+ user_data: str = None
22
+ vcpu: int = 1
23
+ memory: int = 512
24
+ verbose: bool = False
25
+ ssh_user: str = "root"
26
+ expose_ports: bool = False
27
+ host_port: int = None
28
+ dest_port: int = None
29
+ vsock_enabled: bool = False
30
+ vsock_guest_cid: int = 3
@@ -0,0 +1,33 @@
1
+ """Custom exceptions for the Firecracker library."""
2
+
3
+
4
+ class FirecrackerError(Exception):
5
+ """Base exception for all Firecracker errors."""
6
+ def __init__(self, message: str, *args, **kwargs):
7
+ self.message = message
8
+ super().__init__(message, *args)
9
+
10
+
11
+ class NetworkError(FirecrackerError):
12
+ """Network-related errors."""
13
+ pass
14
+
15
+
16
+ class ConfigurationError(FirecrackerError):
17
+ """Configuration-related errors."""
18
+ pass
19
+
20
+
21
+ class VMMError(FirecrackerError):
22
+ """VMM operation errors."""
23
+ pass
24
+
25
+
26
+ class APIError(FirecrackerError):
27
+ """API-related errors."""
28
+ pass
29
+
30
+
31
+ class ProcessError(FirecrackerError):
32
+ """Process management errors."""
33
+ pass
firecracker/logger.py ADDED
@@ -0,0 +1,98 @@
1
+ import logging
2
+
3
+
4
+ class Logger:
5
+ """Custom logger class for MicroVM operations."""
6
+ COLORS = {
7
+ "INFO": "\033[0m",
8
+ "ERROR": "\033[91m",
9
+ "WARNING": "\033[93m",
10
+ "DEBUG": "\033[94m",
11
+ }
12
+ RESET = "\033[0m"
13
+
14
+ LEVEL_MAP = {
15
+ "DEBUG": logging.DEBUG,
16
+ "INFO": logging.INFO,
17
+ "WARN": logging.WARNING,
18
+ "ERROR": logging.ERROR,
19
+ }
20
+
21
+ def __init__(self, level: str = "INFO", verbose: bool = False):
22
+ """Initialize the logger with custom configuration.
23
+
24
+ Args:
25
+ level (str): Initial log level (INFO, ERROR, WARN, DEBUG)
26
+ verbose (bool): Enable verbose (DEBUG) logging
27
+ """
28
+ self.logger = logging.getLogger('microvm')
29
+ self.logger.propagate = False
30
+ self.verbose = verbose
31
+
32
+ for handler in self.logger.handlers[:]:
33
+ self.logger.removeHandler(handler)
34
+
35
+ console_handler = logging.StreamHandler()
36
+ formatter = logging.Formatter(
37
+ "\r[%(asctime)s.%(msecs)03d] [%(colored_levelname)s] %(message)s",
38
+ "%Y-%m-%dT%H:%M:%S"
39
+ )
40
+ console_handler.addFilter(self._add_colored_levelname)
41
+ console_handler.setFormatter(formatter)
42
+ self.logger.addHandler(console_handler)
43
+
44
+ self.set_level(level)
45
+
46
+ def _add_colored_levelname(self, record):
47
+ """Add colored levelname to the log record."""
48
+ level = record.levelname
49
+ if level == "INFO" and getattr(record, "success", False):
50
+ level = "SUCCESS"
51
+ color = self.COLORS.get(level, self.COLORS["INFO"])
52
+ record.colored_levelname = f"{color}{level}{self.RESET}"
53
+ return True
54
+
55
+ def set_level(self, level: str):
56
+ """Set the logging level.
57
+
58
+ Args:
59
+ level (str): Log level to set (INFO, ERROR, WARNING, DEBUG)
60
+ """
61
+ level = level.upper()
62
+ logging_level = self.LEVEL_MAP.get(level, logging.INFO)
63
+ self.logger.setLevel(logging_level)
64
+ self.current_level = level
65
+
66
+ def __call__(self, level: str, message: str):
67
+ """Log a message at the specified level.
68
+
69
+ Args:
70
+ level (str): Level to log at (INFO, ERROR, WARNING, DEBUG)
71
+ message (str): Message to log
72
+ """
73
+ level = level.upper()
74
+ if level not in self.LEVEL_MAP:
75
+ level = "INFO" # Default to INFO for unknown levels
76
+
77
+ msg_level = self.LEVEL_MAP[level]
78
+ current_level = self.LEVEL_MAP[self.current_level]
79
+
80
+ if msg_level >= current_level:
81
+ log_method = getattr(self.logger, level.lower())
82
+ log_method(message)
83
+
84
+ def info(self, message: str):
85
+ """Log an info message."""
86
+ self("INFO", message)
87
+
88
+ def error(self, message: str):
89
+ """Log an error message."""
90
+ self("ERROR", message)
91
+
92
+ def warn(self, message: str):
93
+ """Log a warning message."""
94
+ self("WARN", message)
95
+
96
+ def debug(self, message: str):
97
+ """Log a debug message."""
98
+ self("DEBUG", message)