Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/framework/s3fetcher.py
1957 views
1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Define a class for interacting with microvm images in s3."""
4
5
import os
6
import platform
7
import re
8
9
from shutil import copyfile
10
from typing import List
11
12
import boto3
13
import botocore.client
14
15
16
class MicrovmImageS3Fetcher:
17
"""A borg class for fetching Firecracker microvm images from s3.
18
19
# Microvm Image Bucket Layout
20
21
A microvm image bucket needs to obey a specific layout, described below.
22
23
## Folder Layout
24
25
``` tree
26
s3://<microvm-image-bucket>/<microvm-image-path>/
27
<microvm_image_folder_n>/
28
kernel/
29
<optional_kernel_name>vmlinux.bin
30
fsfiles/
31
<optional_initrd_name>initrd.img
32
<rootfs_file_name>rootfs.ext4
33
<other_fsfile_n>
34
...
35
<other_resource_n>
36
...
37
...
38
```
39
40
## Tagging
41
42
Microvm image folders are tagged with the capabilities of that image:
43
44
``` json
45
TagSet = [{"key": "capability:<cap_name>", "value": ""}, ...]
46
```
47
48
# Credentials
49
50
`boto3` is configured to not perform the signing step at all by using
51
the `signature_version=UNSIGNED` so no credentials are needed. Thus, the
52
bucket where the microVM images are stored needs to be publicly accessible.
53
"""
54
55
MICROVM_IMAGES_RELPATH = 'img/' + platform.machine() + '/'
56
MICROVM_IMAGE_KERNEL_RELPATH = 'kernel/'
57
MICROVM_IMAGE_BLOCKDEV_RELPATH = 'fsfiles/'
58
MICROVM_IMAGE_KERNEL_FILE_SUFFIX = r'vmlinux.bin'
59
MICROVM_IMAGE_INITRD_FILE_SUFFIX = r'initrd.img'
60
MICROVM_IMAGE_ROOTFS_FILE_SUFFIX = r'rootfs.ext4'
61
MICROVM_IMAGE_SSH_KEY_SUFFIX = r'.id_rsa'
62
63
ENV_LOCAL_IMAGES_PATH_VAR = 'OPT_LOCAL_IMAGES_PATH'
64
65
CAPABILITY_KEY_PREFIX = 'capability:'
66
67
_microvm_images = None
68
_microvm_images_by_cap = None
69
_microvm_images_bucket = None
70
_s3 = None
71
72
def __init__(
73
self,
74
microvm_images_bucket
75
):
76
"""Initialize S3 client, list of microvm images and S3 bucket name."""
77
self._microvm_images_bucket = microvm_images_bucket
78
self._s3 = boto3.client(
79
's3',
80
config=botocore.client.Config(signature_version=botocore.UNSIGNED)
81
)
82
self._map_bucket()
83
assert self._microvm_images and self._microvm_images_by_cap
84
85
def init_vm_resources(self, microvm_image_name, microvm):
86
"""Populate the microvm resource path with the necessary resources.
87
88
Assumes the correct microvm image structure, and copies all
89
microvm image resources into the microvm resource path.
90
"""
91
for resource_key in self._microvm_images[microvm_image_name]:
92
if resource_key in [
93
self.MICROVM_IMAGE_KERNEL_RELPATH,
94
self.MICROVM_IMAGE_BLOCKDEV_RELPATH
95
]:
96
# Kernel and blockdev dirs already exist in the microvm's
97
# allocated resources.
98
continue
99
100
microvm_dest_path = os.path.join(microvm.path, resource_key)
101
if resource_key.endswith('/'):
102
# Create a new microvm_directory if one is encountered.
103
os.mkdir(microvm_dest_path)
104
continue
105
106
image_rel_path = os.path.join(
107
self.MICROVM_IMAGES_RELPATH,
108
microvm_image_name
109
)
110
111
# Relative path of a microvm resource within a microvm directory.
112
resource_rel_path = os.path.join(
113
image_rel_path,
114
resource_key
115
)
116
117
if self.ENV_LOCAL_IMAGES_PATH_VAR in os.environ:
118
# There's a user-managed local microvm image directory.
119
resource_root_path = (
120
os.environ.get(self.ENV_LOCAL_IMAGES_PATH_VAR)
121
)
122
else:
123
# Use a root path in the temporary test session directory.
124
resource_root_path = microvm.path
125
126
# Local path of a microvm resource. Used for downloading resources
127
# only once.
128
resource_local_path = os.path.join(
129
resource_root_path,
130
resource_rel_path
131
)
132
133
if not os.path.exists(resource_local_path):
134
# Locally create / download an s3 resource the first time we
135
# encounter it.
136
os.makedirs(
137
os.path.dirname(resource_local_path),
138
exist_ok=True
139
)
140
self._s3.download_file(
141
self._microvm_images_bucket,
142
resource_rel_path,
143
resource_local_path)
144
145
if not os.path.exists(microvm_dest_path):
146
copyfile(resource_local_path, microvm_dest_path)
147
148
if resource_key.endswith(self.MICROVM_IMAGE_KERNEL_FILE_SUFFIX):
149
microvm.kernel_file = microvm_dest_path
150
151
if resource_key.endswith(self.MICROVM_IMAGE_ROOTFS_FILE_SUFFIX):
152
microvm.rootfs_file = microvm_dest_path
153
154
if resource_key.endswith(self.MICROVM_IMAGE_INITRD_FILE_SUFFIX):
155
microvm.initrd_file = microvm_dest_path
156
157
if resource_key.endswith(self.MICROVM_IMAGE_SSH_KEY_SUFFIX):
158
# Add the key path to the config dictionary and set
159
# permissions.
160
microvm.ssh_config['ssh_key_path'] = microvm_dest_path
161
os.chmod(microvm_dest_path, 0o400)
162
163
def hardlink_vm_resources(
164
self,
165
microvm_image_name,
166
from_microvm,
167
to_microvm
168
):
169
"""Hardlink resources from one microvm to another.
170
171
Assumes the correct microvm image structure for the source vm specified
172
by the `from_microvm` parameter and copies all necessary resources into
173
the destination microvm specified through the `to_microvm` parameter.
174
"""
175
for resource_key in self._microvm_images[microvm_image_name]:
176
if resource_key in [
177
self.MICROVM_IMAGE_KERNEL_RELPATH,
178
self.MICROVM_IMAGE_BLOCKDEV_RELPATH
179
]:
180
# Kernel and blockdev dirs already exist in the microvm's
181
# allocated resources.
182
continue
183
184
microvm_dest_path = os.path.join(to_microvm.path, resource_key)
185
microvm_source_path = os.path.join(from_microvm.path, resource_key)
186
187
if resource_key.endswith('/'):
188
# Create a new microvm_directory if one is encountered.
189
os.mkdir(microvm_dest_path)
190
continue
191
192
if not os.path.exists(microvm_dest_path):
193
os.link(microvm_source_path, microvm_dest_path)
194
195
if resource_key.endswith(self.MICROVM_IMAGE_KERNEL_FILE_SUFFIX):
196
to_microvm.kernel_file = microvm_dest_path
197
198
if resource_key.endswith(self.MICROVM_IMAGE_ROOTFS_FILE_SUFFIX):
199
to_microvm.rootfs_file = microvm_dest_path
200
201
if resource_key.endswith(self.MICROVM_IMAGE_INITRD_FILE_SUFFIX):
202
to_microvm.initrd_file = microvm_dest_path
203
204
if resource_key.endswith(self.MICROVM_IMAGE_SSH_KEY_SUFFIX):
205
# Add the key path to the config dictionary and set
206
# permissions.
207
to_microvm.ssh_config['ssh_key_path'] = microvm_dest_path
208
os.chmod(microvm_dest_path, 0o400)
209
210
def list_microvm_images(self, capability_filter: List[str] = None):
211
"""Return microvm images with the specified capabilities."""
212
capability_filter = capability_filter or ['*']
213
microvm_images_with_caps = []
214
for cap in capability_filter:
215
if cap == '*':
216
microvm_images_with_caps.append({*self._microvm_images})
217
continue
218
microvm_images_with_caps.append(self._microvm_images_by_cap[cap])
219
220
return list(set.intersection(*microvm_images_with_caps))
221
222
def enum_capabilities(self):
223
"""Return a list of all the capabilities of all microvm images."""
224
return [*self._microvm_images_by_cap]
225
226
def _map_bucket(self):
227
"""Map all the keys and tags in the s3 microvm image bucket.
228
229
This allows the other methods to work on local objects.
230
231
Populates `self._microvm_images` with
232
{microvm_image_folder_key_n: [microvm_image_key_n, ...], ...}
233
234
Populates `self._microvm_images_by_cap` with a capability dict:
235
`{capability_n: {microvm_image_folder_key_n, ...}, ...}
236
"""
237
self._microvm_images = {}
238
self._microvm_images_by_cap = {}
239
folder_key_groups_regex = re.compile(
240
self.MICROVM_IMAGES_RELPATH + r'(.+?)/(.*)'
241
)
242
243
for obj in self._s3.list_objects_v2(
244
Bucket=self._microvm_images_bucket,
245
Prefix=self.MICROVM_IMAGES_RELPATH
246
)['Contents']:
247
key_groups = re.match(folder_key_groups_regex, obj['Key'])
248
if key_groups is None:
249
# Ignore files (leaves) under MICROVM_IMAGES_RELPATH
250
continue
251
microvm_image_name = key_groups.group(1)
252
resource = key_groups.group(2)
253
254
if not resource:
255
# This is a microvm image root folder.
256
self._microvm_images[microvm_image_name] = []
257
for cap in self._get_caps(obj['Key']):
258
if cap not in self._microvm_images_by_cap:
259
self._microvm_images_by_cap[cap] = set()
260
self._microvm_images_by_cap[cap].add(microvm_image_name)
261
else:
262
# This is key within a microvm image root folder.
263
self._microvm_images[microvm_image_name].append(resource)
264
265
def _get_caps(self, key):
266
"""Return the set of capabilities of an s3 object key."""
267
tagging = self._s3.get_object_tagging(
268
Bucket=self._microvm_images_bucket,
269
Key=key
270
)
271
return {
272
tag['Key'][len(self.CAPABILITY_KEY_PREFIX):]
273
for tag in tagging['TagSet']
274
if tag['Key'].startswith(self.CAPABILITY_KEY_PREFIX)
275
}
276
277