Skip to content

Commit 16cf8f3

Browse files
authored
Merge branch 'firecracker-microvm:main' into rustify-snapshot-module
2 parents d5c72c6 + f91b800 commit 16cf8f3

File tree

246 files changed

+4349
-20974
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

246 files changed

+4349
-20974
lines changed

.buildkite/common.py

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import argparse
99
import json
1010
import os
11+
import random
12+
import string
1113
import subprocess
1214
from pathlib import Path
1315

@@ -21,7 +23,6 @@
2123
]
2224

2325
DEFAULT_PLATFORMS = [
24-
("al2", "linux_4.14"),
2526
("al2", "linux_5.10"),
2627
("al2023", "linux_6.1"),
2728
]
@@ -70,12 +71,12 @@ def group(label, command, instances, platforms, **kwargs):
7071
if isinstance(command, str):
7172
commands = [command]
7273
for instance in instances:
73-
for os, kv in platforms:
74+
for os_, kv in platforms:
7475
# fill any templated variables
75-
args = {"instance": instance, "os": os, "kv": kv}
76+
args = {"instance": instance, "os": os_, "kv": kv}
7677
step = {
7778
"command": [cmd.format(**args) for cmd in commands],
78-
"label": f"{label1} {instance} {os} {kv}",
79+
"label": f"{label1} {instance} {os_} {kv}",
7980
"agents": args,
8081
}
8182
step_kwargs = dict_fmt(kwargs, args)
@@ -85,11 +86,6 @@ def group(label, command, instances, platforms, **kwargs):
8586
return {"group": label, "steps": steps}
8687

8788

88-
def pipeline_to_json(pipeline):
89-
"""Serialize a pipeline dictionary to JSON"""
90-
return json.dumps(pipeline, indent=4, sort_keys=True, ensure_ascii=False)
91-
92-
9389
def get_changed_files():
9490
"""
9591
Get all files changed since `branch`
@@ -119,23 +115,6 @@ def run_all_tests(changed_files):
119115
)
120116

121117

122-
def devtool_test(devtool_opts=None, pytest_opts=None, binary_dir=None):
123-
"""Generate a `devtool test` command"""
124-
cmds = []
125-
parts = ["./tools/devtool -y test"]
126-
if devtool_opts:
127-
parts.append(devtool_opts)
128-
parts.append("--")
129-
if binary_dir is not None:
130-
cmds.append(f'buildkite-agent artifact download "{binary_dir}/$(uname -m)/*" .')
131-
cmds.append(f"chmod -v a+x {binary_dir}/**/*")
132-
parts.append(f"--binary-dir=../{binary_dir}/$(uname -m)")
133-
if pytest_opts:
134-
parts.append(pytest_opts)
135-
cmds.append(" ".join(parts))
136-
return cmds
137-
138-
139118
class DictAction(argparse.Action):
140119
"""An argparse action that can receive a nested dictionary
141120
@@ -192,3 +171,169 @@ def __call__(self, parser, namespace, value, option_string=None):
192171
default=None,
193172
type=str,
194173
)
174+
175+
176+
def random_str(k: int):
177+
"""Generate a random string of hex characters."""
178+
return "".join(random.choices(string.hexdigits, k=k))
179+
180+
181+
def ab_revision_build(revision):
182+
"""Generate steps for building an A/B-test revision"""
183+
# Copied from framework/ab_test. Double dollar signs needed for Buildkite (otherwise it will try to interpolate itself)
184+
return [
185+
f"commitish={revision}",
186+
f"if ! git cat-file -t $$commitish; then commitish=origin/{revision}; fi",
187+
"branch_name=tmp-$$commitish",
188+
"git branch $$branch_name $$commitish",
189+
f"git clone -b $$branch_name . build/{revision}",
190+
f"cd build/{revision} && ./tools/devtool -y build --release && cd -",
191+
]
192+
193+
194+
def shared_build():
195+
"""Helper function to make it simple to share a compilation artifacts for a
196+
whole Buildkite build
197+
"""
198+
199+
# We need to support 3 scenarios here:
200+
# 1. We are running in the nightly pipeline - only compile the HEAD of main.
201+
# 2. We are running in a PR pipeline - compile HEAD of main as revision A and HEAD of PR branch as revision B.
202+
# 3. We are running in an A/B-test pipeline - compile what is passed via REVISION_{A,B} environment variables.
203+
rev_a = os.environ.get("REVISION_A")
204+
if rev_a is not None:
205+
rev_b = os.environ.get("REVISION_B")
206+
assert rev_b is not None, "REVISION_B environment variable not set"
207+
build_cmds = ab_revision_build(rev_a) + ab_revision_build(rev_b)
208+
elif os.environ.get("BUILDKITE_PULL_REQUEST", "false") != "false":
209+
build_cmds = ab_revision_build(
210+
os.environ.get("BUILDKITE_PULL_REQUEST_BASE_BRANCH", "main")
211+
) + ["./tools/devtool -y build --release"]
212+
else:
213+
build_cmds = ["./tools/devtool -y build --release"]
214+
binary_dir = f"build_$(uname -m)_{random_str(k=8)}.tar.gz"
215+
build_cmds += [
216+
"du -sh build/*",
217+
f"tar czf {binary_dir} build",
218+
f"buildkite-agent artifact upload {binary_dir}",
219+
]
220+
return build_cmds, binary_dir
221+
222+
223+
class BKPipeline:
224+
"""
225+
Buildkite Pipeline class abstraction
226+
227+
Helper class to easily construct pipelines.
228+
"""
229+
230+
parser = COMMON_PARSER
231+
232+
def __init__(self, initial_steps=None, **kwargs):
233+
self.steps = []
234+
self.args = args = self.parser.parse_args()
235+
# Retry one time if agent was lost. This can happen if we terminate the
236+
# instance or the agent gets disconnected for whatever reason
237+
retry = {
238+
"automatic": [{"exit_status": -1, "limit": 1}],
239+
}
240+
retry = overlay_dict(retry, kwargs.pop("retry", {}))
241+
# Calculate step defaults with parameters and kwargs
242+
per_instance = {
243+
"instances": args.instances,
244+
"platforms": args.platforms,
245+
"artifact_paths": ["./test_results/**/*"],
246+
"retry": retry,
247+
**kwargs,
248+
}
249+
self.per_instance = overlay_dict(per_instance, args.step_param)
250+
self.per_arch = self.per_instance.copy()
251+
self.per_arch["instances"] = ["m6i.metal", "m7g.metal"]
252+
self.per_arch["platforms"] = [("al2", "linux_5.10")]
253+
self.binary_dir = args.binary_dir
254+
# Build sharing
255+
build_cmds, self.shared_build = shared_build()
256+
step_build = group("🏗️ Build", build_cmds, **self.per_arch)
257+
self.steps += [step_build, "wait"]
258+
259+
# If we run initial_steps before the "wait" step above, then a failure of the initial steps
260+
# would result in the build not progressing past the "wait" step (as buildkite only proceeds past a wait step
261+
# if everything before it passed). Thus put the initial steps after the "wait" step, but set `"depends_on": null`
262+
# to start running them immediately (e.g. without waiting for the "wait" step to unblock).
263+
#
264+
# See also https://buildkite.com/docs/pipelines/dependencies#explicit-dependencies-in-uploaded-steps
265+
if initial_steps:
266+
for step in initial_steps:
267+
step["depends_on"] = None
268+
269+
self.steps += initial_steps
270+
271+
def add_step(self, step, decorate=True):
272+
"""
273+
Add a step to the pipeline.
274+
275+
https://buildkite.com/docs/pipelines/step-reference
276+
277+
:param step: a Buildkite step
278+
:param decorate: inject needed commands for sharing builds
279+
"""
280+
if decorate and isinstance(step, dict):
281+
step = self._adapt_group(step)
282+
self.steps.append(step)
283+
return step
284+
285+
def _adapt_group(self, group):
286+
""""""
287+
prepend = [
288+
f'buildkite-agent artifact download "{self.shared_build}" .',
289+
f"tar xzf {self.shared_build}",
290+
]
291+
if self.binary_dir is not None:
292+
prepend.extend(
293+
[
294+
f'buildkite-agent artifact download "{self.binary_dir}/$(uname -m)/*" .',
295+
f"chmod -v a+x {self.binary_dir}/**/*",
296+
]
297+
)
298+
299+
for step in group["steps"]:
300+
step["command"] = prepend + step["command"]
301+
return group
302+
303+
def build_group(self, *args, **kwargs):
304+
"""
305+
Build a group, parametrizing over the selected instances/platforms.
306+
307+
https://buildkite.com/docs/pipelines/group-step
308+
"""
309+
combined = overlay_dict(self.per_instance, kwargs)
310+
return self.add_step(group(*args, **combined))
311+
312+
def build_group_per_arch(self, *args, **kwargs):
313+
"""
314+
Build a group, parametrizing over the architectures only.
315+
"""
316+
combined = overlay_dict(self.per_arch, kwargs)
317+
return self.add_step(group(*args, **combined))
318+
319+
def to_dict(self):
320+
"""Render the pipeline as a dictionary."""
321+
return {"steps": self.steps}
322+
323+
def to_json(self):
324+
"""Serialize the pipeline to JSON"""
325+
return json.dumps(self.to_dict(), indent=4, sort_keys=True, ensure_ascii=False)
326+
327+
def devtool_test(self, devtool_opts=None, pytest_opts=None):
328+
"""Generate a `devtool test` command"""
329+
cmds = []
330+
parts = ["./tools/devtool -y test", "--no-build"]
331+
if devtool_opts:
332+
parts.append(devtool_opts)
333+
parts.append("--")
334+
if self.binary_dir is not None:
335+
parts.append(f"--binary-dir=../{self.binary_dir}/$(uname -m)")
336+
if pytest_opts:
337+
parts.append(pytest_opts)
338+
cmds.append(" ".join(parts))
339+
return cmds

.buildkite/pipeline_cpu_template.py

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
"""Generate Buildkite CPU Template pipelines dynamically"""
66

7-
import argparse
87
from enum import Enum
98

10-
from common import DEFAULT_INSTANCES, DEFAULT_PLATFORMS, group, pipeline_to_json
9+
from common import DEFAULT_INSTANCES, DEFAULT_PLATFORMS, BKPipeline, group
1110

1211

1312
class BkStep(str, Enum):
@@ -17,23 +16,31 @@ class BkStep(str, Enum):
1716

1817
LABEL = "label"
1918
TIMEOUT = "timeout"
20-
COMMAND = "commands"
19+
COMMAND = "command"
2120
ARTIFACTS = "artifact_paths"
2221

2322

2423
cpu_template_test = {
2524
"rdmsr": {
2625
BkStep.COMMAND: [
27-
"tools/devtool -y test -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_rdmsr' "
26+
"tools/devtool -y test --no-build -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_rdmsr' "
2827
],
2928
BkStep.LABEL: "📖 rdmsr",
3029
"instances": ["c5n.metal", "m5n.metal", "m6a.metal", "m6i.metal"],
3130
"platforms": DEFAULT_PLATFORMS,
3231
},
32+
"fingerprint": {
33+
BkStep.COMMAND: [
34+
"tools/devtool -y test --no-build -- -m no_block_pr integration_tests/functional/test_cpu_template_helper.py -k test_guest_cpu_config_change",
35+
],
36+
BkStep.LABEL: "🖐️ fingerprint",
37+
"instances": DEFAULT_INSTANCES,
38+
"platforms": DEFAULT_PLATFORMS,
39+
},
3340
"cpuid_wrmsr": {
3441
"snapshot": {
3542
BkStep.COMMAND: [
36-
"tools/devtool -y test -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_wrmsr_snapshot or test_cpu_cpuid_snapshot'",
43+
"tools/devtool -y test --no-build -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_wrmsr_snapshot or test_cpu_cpuid_snapshot'",
3744
"mkdir -pv tests/snapshot_artifacts_upload/{instance}_{os}_{kv}",
3845
"sudo mv tests/snapshot_artifacts/* tests/snapshot_artifacts_upload/{instance}_{os}_{kv}",
3946
],
@@ -45,7 +52,7 @@ class BkStep(str, Enum):
4552
BkStep.COMMAND: [
4653
"buildkite-agent artifact download tests/snapshot_artifacts_upload/{instance}_{os}_{kv}/**/* .",
4754
"mv tests/snapshot_artifacts_upload/{instance}_{os}_{kv} tests/snapshot_artifacts",
48-
"tools/devtool -y test -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_wrmsr_restore or test_cpu_cpuid_restore'",
55+
"tools/devtool -y test --no-build -- -s -ra -m nonci -n4 --log-cli-level=INFO integration_tests/functional/test_cpu_features.py -k 'test_cpu_wrmsr_restore or test_cpu_cpuid_restore'",
4956
],
5057
BkStep.LABEL: "📸 load snapshot artifacts created on {instance} {snapshot_os} {snapshot_kv} to {restore_instance} {restore_os} {restore_kv}",
5158
BkStep.TIMEOUT: 30,
@@ -57,33 +64,9 @@ class BkStep(str, Enum):
5764
},
5865
"instances": ["c5n.metal", "m5n.metal", "m6i.metal", "m6a.metal"],
5966
},
60-
"fingerprint": {
61-
BkStep.COMMAND: [
62-
"tools/devtool -y test -- -m no_block_pr integration_tests/functional/test_cpu_template_helper.py -k test_guest_cpu_config_change",
63-
],
64-
BkStep.LABEL: "🖐️ fingerprint",
65-
"instances": DEFAULT_INSTANCES,
66-
"platforms": DEFAULT_PLATFORMS,
67-
},
6867
}
6968

7069

71-
def group_single(tests):
72-
"""
73-
Generate a group step with specified parameters for each instance
74-
and kernel combination
75-
https://buildkite.com/docs/pipelines/group-step
76-
"""
77-
group_step = group(
78-
label=tests[BkStep.LABEL],
79-
command=tests[BkStep.COMMAND],
80-
instances=tests["instances"],
81-
platforms=tests["platforms"],
82-
artifacts=["./test_results/**/*"],
83-
)
84-
return [group_step]
85-
86-
8770
def group_snapshot_restore(test_step):
8871
"""
8972
Generate a group step with specified parameters for each instance
@@ -146,30 +129,20 @@ def group_snapshot_restore(test_step):
146129
return groups
147130

148131

149-
def main():
150-
"""
151-
Generate group template required to trigger pipelines for
152-
the requested CPU template test.
153-
"""
154-
parser = argparse.ArgumentParser()
155-
parser.add_argument(
132+
if __name__ == "__main__":
133+
BKPipeline.parser.add_argument(
156134
"--test",
157-
required=True,
158135
choices=list(cpu_template_test),
159136
help="CPU template test",
137+
action="append",
160138
)
161-
test_args = parser.parse_args()
162-
163-
if test_args.test == "rdmsr":
164-
test_group = group_single(cpu_template_test[test_args.test])
165-
elif test_args.test == "cpuid_wrmsr":
166-
test_group = group_snapshot_restore(cpu_template_test[test_args.test])
167-
elif test_args.test == "fingerprint":
168-
test_group = group_single(cpu_template_test[test_args.test])
169-
170-
pipeline = {"steps": test_group}
171-
print(pipeline_to_json(pipeline))
172-
173-
174-
if __name__ == "__main__":
175-
main()
139+
pipeline = BKPipeline()
140+
for test in pipeline.args.test or list(cpu_template_test):
141+
if test == "cpuid_wrmsr":
142+
groups = group_snapshot_restore(cpu_template_test[test])
143+
for grp in groups:
144+
pipeline.add_step(grp)
145+
else:
146+
test_data = cpu_template_test[test]
147+
pipeline.build_group(**test_data, artifacts=["./test_results/**/*"])
148+
print(pipeline.to_json())

0 commit comments

Comments
 (0)