Skip to content

Commit 79098f5

Browse files
alsrdnclaudetomassrnka
authored andcommitted
fix(orchestrator): wrap reboot-cpu-template-override so {} means no override
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Tomas Srnka <tomassrnka@users.noreply.github.com> GitOrigin-RevId: 92e91408e610dc17e99b4d2d9ab0f9b093774bab
1 parent 9cc9ca7 commit 79098f5

5 files changed

Lines changed: 84 additions & 13 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//go:build linux
2+
3+
package main
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestCPUTemplateOverride(t *testing.T) {
13+
t.Parallel()
14+
15+
for raw, want := range map[string]string{
16+
`{"kvm_capabilities":["!122"]}`: `{"template":{"kvm_capabilities":["!122"]}}`,
17+
`{}`: `{"template":{}}`,
18+
"null\n": `{"template":{}}`,
19+
"": `{"template":{}}`,
20+
} {
21+
got, err := cpuTemplateOverride([]byte(raw))
22+
require.NoError(t, err, raw)
23+
assert.JSONEq(t, want, got.JSONString(), raw)
24+
}
25+
26+
_, err := cpuTemplateOverride([]byte(`{"kvm_capabilities":`))
27+
require.Error(t, err)
28+
}

‎packages/orchestrator/cmd/resume-build/main.go‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
package main
44

55
import (
6+
"bytes"
67
"context"
8+
"encoding/json"
79
"errors"
810
"flag"
911
"fmt"
@@ -50,6 +52,23 @@ import (
5052
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
5153
)
5254

55+
// cpuTemplateOverride wraps a -cpu-template document as the reboot-cpu-template-override
56+
// value. Empty and null documents mean no template, so they wrap as {} rather than as null,
57+
// which the flag reads as no override.
58+
func cpuTemplateOverride(raw []byte) (ldvalue.Value, error) {
59+
tmpl := ldvalue.ObjectBuild().Build()
60+
if raw = bytes.TrimSpace(raw); len(raw) > 0 {
61+
if !json.Valid(raw) {
62+
return ldvalue.Null(), errors.New("not valid JSON")
63+
}
64+
if parsed := ldvalue.Parse(raw); !parsed.IsNull() {
65+
tmpl = parsed
66+
}
67+
}
68+
69+
return ldvalue.ObjectBuild().Set("template", tmpl).Build(), nil
70+
}
71+
5372
func main() {
5473
fromBuild := flag.String("from-build", "", "build ID (UUID) to resume from (required)")
5574
toBuild := flag.String("to-build", "", "output build ID (UUID) for pause snapshot (auto-generated if not specified)")
@@ -171,7 +190,11 @@ func main() {
171190
log.Fatalf("read -cpu-template: %v", err)
172191
}
173192
}
174-
featureflags.OverrideJSONFlag(featureflags.RebootCPUTemplateOverride, ldvalue.Parse(raw))
193+
override, err := cpuTemplateOverride(raw)
194+
if err != nil {
195+
log.Fatalf("-cpu-template: %v", err)
196+
}
197+
featureflags.OverrideJSONFlag(featureflags.RebootCPUTemplateOverride, override)
175198
}
176199

177200
if *fromBuild == "" {

‎packages/orchestrator/pkg/sandbox/reboot.go‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ package sandbox
44

55
import (
66
"context"
7+
"encoding/json"
78
"errors"
89
"fmt"
10+
"strings"
911
"sync"
1012
"time"
1113

@@ -85,11 +87,19 @@ func checkRebootCPUTemplate(tmpl *cputemplate.Template, fcVersion string) error
8587
// boot ran, unless override (the reboot-cpu-template-override flag) replaces it. An invalid
8688
// override is returned as an error alongside the build's template.
8789
func rebootCPUTemplate(meta metadata.Template, override ldvalue.Value) (*cputemplate.Template, bool, error) {
88-
if override.IsNull() {
90+
var o struct {
91+
Template *json.RawMessage `json:"template"`
92+
}
93+
dec := json.NewDecoder(strings.NewReader(override.JSONString()))
94+
dec.DisallowUnknownFields()
95+
if err := dec.Decode(&o); err != nil {
96+
return meta.BuildCPUTemplate, false, fmt.Errorf("invalid CPU template override: %w", err)
97+
}
98+
if o.Template == nil {
8999
return meta.BuildCPUTemplate, false, nil
90100
}
91101

92-
tmpl, err := cputemplate.Parse([]byte(override.JSONString()))
102+
tmpl, err := cputemplate.Parse(*o.Template)
93103
if err != nil {
94104
return meta.BuildCPUTemplate, false, err
95105
}

‎packages/orchestrator/pkg/sandbox/reboot_cpu_template_test.go‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,27 @@ func TestRebootCPUTemplate(t *testing.T) {
5252
assert.False(t, overridden)
5353
assert.Equal(t, a, got, "with no override the build's template returns")
5454

55-
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(`{"kvm_capabilities":["!122"]}`)))
55+
for _, none := range []string{`{}`, `{"template":null}`} {
56+
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(none)))
57+
require.NoError(t, err)
58+
assert.False(t, overridden)
59+
assert.Equal(t, a, got, "%s is no override", none)
60+
}
61+
62+
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(`{"template":{"kvm_capabilities":["!122"]}}`)))
5663
require.NoError(t, err)
5764
assert.True(t, overridden)
5865
assert.Equal(t, b, got)
5966

60-
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(`{}`)))
67+
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(`{"template":{}}`)))
6168
require.NoError(t, err)
6269
assert.True(t, overridden)
63-
assert.Nil(t, got, "{} boots with no template")
70+
assert.Nil(t, got, `{"template":{}} boots with no template`)
6471

65-
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(`{"bogus":1}`)))
66-
require.Error(t, err)
67-
assert.False(t, overridden)
68-
assert.Equal(t, a, got, "an invalid override falls back to the build's template")
72+
for _, invalid := range []string{`{"template":{"bogus":1}}`, `{"kvm_capabilities":["!122"]}`, `{"template":{},"x":1}`, `[]`} {
73+
got, overridden, err = rebootCPUTemplate(meta, ldvalue.Parse([]byte(invalid)))
74+
require.Error(t, err, invalid)
75+
assert.False(t, overridden)
76+
assert.Equal(t, a, got, "an invalid override falls back to the build's template")
77+
}
6978
}

‎packages/shared/pkg/featureflags/flags.go‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,9 +1046,10 @@ var (
10461046
BuildCPUTemplate = NewJSONFlag("build-cpu-template", ldvalue.Null())
10471047

10481048
// RebootCPUTemplateOverride replaces the build's CPU template on a filesystem-only cold
1049-
// boot, as the PUT /cpu-config body; {} boots with none. Null (the default) boots the
1050-
// build's template. The applied template is recorded as the running one, so the next
1051-
// pause stores it, while the build's template is kept and returns once the flag clears.
1049+
// boot: {"template": <PUT /cpu-config body>}; {"template": {}} boots with none. {} or null
1050+
// (the default) boots the build's template. The applied template is recorded as the
1051+
// running one, so the next pause stores it, while the build's template is kept and
1052+
// returns once the flag clears.
10521053
// A value that does not parse is logged and ignored; one the resolved Firecracker version
10531054
// or host cannot apply fails the boot, like a stored template would.
10541055
RebootCPUTemplateOverride = NewJSONFlag("reboot-cpu-template-override", ldvalue.Null())

0 commit comments

Comments
 (0)