Skip to content

Commit 25f042a

Browse files
feat: implement typed storage support in runtime preparation (#21)
1 parent 5710eec commit 25f042a

5 files changed

Lines changed: 181 additions & 14 deletions

File tree

schedune-control-plane/internal/runtime/cloudhypervisor.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ type CloudHypervisorExecutor struct{}
1414
func (k *CloudHypervisorExecutor) Prepare(spec launch.LaunchSpec) (launch.PreparedLaunch, error) {
1515
binPath := "cloud-hypervisor"
1616

17-
artifactPath := spec.ImageReference
17+
artifactPath, _ := resolvePrimaryDisk(spec)
18+
19+
if artifactPath == "" {
20+
return launch.PreparedLaunch{}, fmt.Errorf("artifact missing for execution")
21+
}
22+
1823
if _, err := os.Stat(artifactPath); os.IsNotExist(err) {
1924
return launch.PreparedLaunch{}, fmt.Errorf("artifact missing at host path: %s", artifactPath)
2025
}

schedune-control-plane/internal/runtime/firecracker_validate.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ type FirecrackerExecutor struct{}
1212
func (k *FirecrackerExecutor) Prepare(spec launch.LaunchSpec) (launch.PreparedLaunch, error) {
1313
binPath := "firecracker"
1414

15-
if spec.KernelImagePath == "" || spec.RootfsPath == "" {
15+
kernel, rootfs := resolveFirecrackerDisks(spec)
16+
17+
if kernel == "" || rootfs == "" {
1618
return launch.PreparedLaunch{}, fmt.Errorf("missing kernel or rootfs path for firecracker artifact model")
1719
}
1820

@@ -29,8 +31,8 @@ func (k *FirecrackerExecutor) Prepare(spec launch.LaunchSpec) (launch.PreparedLa
2931
Vcpu: spec.Vcpu,
3032
Firecracker: &launch.PreparedFirecrackerLaunch{
3133
BinaryPath: binPath,
32-
KernelImagePath: spec.KernelImagePath,
33-
RootfsPath: spec.RootfsPath,
34+
KernelImagePath: kernel,
35+
RootfsPath: rootfs,
3436
CommandArgs: args,
3537
},
3638
}, nil

schedune-control-plane/internal/runtime/kvm.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ func (k *KvmExecutor) Prepare(spec launch.LaunchSpec) (launch.PreparedLaunch, er
2222
// For V0 MVP, resolve strictly to the qemu-system binary for the requested arch
2323
binPath := "qemu-system-" + spec.Architecture
2424

25-
artifactPath := spec.ImageReference
25+
artifactPath, format := resolvePrimaryDisk(spec)
26+
27+
if artifactPath == "" {
28+
return launch.PreparedLaunch{}, fmt.Errorf("artifact missing for execution")
29+
}
2630

2731
if _, err := os.Stat(artifactPath); os.IsNotExist(err) {
2832
return launch.PreparedLaunch{}, fmt.Errorf("artifact missing at host path: %s", artifactPath)
@@ -31,7 +35,7 @@ func (k *KvmExecutor) Prepare(spec launch.LaunchSpec) (launch.PreparedLaunch, er
3135
args := []string{
3236
"-m", fmt.Sprintf("%d", spec.MemoryMB),
3337
"-smp", fmt.Sprintf("%d", spec.Vcpu),
34-
"-drive", fmt.Sprintf("file=%s,format=qcow2", artifactPath),
38+
"-drive", fmt.Sprintf("file=%s,format=%s", artifactPath, format),
3539
"-nographic",
3640
}
3741

schedune-control-plane/internal/runtime/kvm_test.go

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package runtime
33
import (
44
"github.com/TechnologyTailors/Schedune/schedune-control-plane/pkg/schema/launch"
55
"os"
6+
"strings"
67
"testing"
78
)
89

910
func TestKvmExecutor_PrepareMissingImage(t *testing.T) {
1011
exec := &KvmExecutor{}
1112

1213
spec := launch.LaunchSpec{
13-
Architecture: "aarch64",
14-
ImageReference: "/tmp/non_existent_image_12345.qcow2",
15-
Vcpu: 2,
16-
MemoryMB: 1024,
14+
Architecture: "aarch64",
15+
Storage: []launch.StorageAttachmentSpec{
16+
{HostPath: "/tmp/non_existent_image_12345.qcow2", Format: "qcow2"},
17+
},
18+
Vcpu: 2,
19+
MemoryMB: 1024,
1720
}
1821

1922
_, err := exec.Prepare(spec)
@@ -22,10 +25,9 @@ func TestKvmExecutor_PrepareMissingImage(t *testing.T) {
2225
}
2326
}
2427

25-
func TestKvmExecutor_PrepareValidImage(t *testing.T) {
28+
func TestKvmExecutor_PrepareValidImageLegacy(t *testing.T) {
2629
exec := &KvmExecutor{}
2730

28-
// create a dummy file
2931
f, err := os.CreateTemp("", "dummy_img_*.qcow2")
3032
if err != nil {
3133
t.Fatalf("could not create temp file: %v", err)
@@ -52,8 +54,120 @@ func TestKvmExecutor_PrepareValidImage(t *testing.T) {
5254
t.Errorf("expected qemu-system-aarch64, got %s", prep.KvmQemu.BinaryPath)
5355
}
5456

55-
if len(prep.KvmQemu.CommandArgs) < 6 {
56-
t.Errorf("expected populated command args, got %v", prep.KvmQemu.CommandArgs)
57+
foundDrive := false
58+
for _, arg := range prep.KvmQemu.CommandArgs {
59+
if strings.Contains(arg, "format=qcow2") {
60+
foundDrive = true
61+
}
62+
}
63+
if !foundDrive {
64+
t.Errorf("expected format=qcow2 in args, got %v", prep.KvmQemu.CommandArgs)
65+
}
66+
}
67+
68+
func TestKvmExecutor_PrepareValidImageTyped(t *testing.T) {
69+
exec := &KvmExecutor{}
70+
71+
f, err := os.CreateTemp("", "dummy_img_*.raw")
72+
if err != nil {
73+
t.Fatalf("could not create temp file: %v", err)
74+
}
75+
defer os.Remove(f.Name())
76+
77+
spec := launch.LaunchSpec{
78+
Architecture: "x86_64",
79+
Storage: []launch.StorageAttachmentSpec{
80+
{HostPath: f.Name(), Format: "raw"},
81+
},
82+
Vcpu: 2,
83+
MemoryMB: 1024,
84+
}
85+
86+
prep, err := exec.Prepare(spec)
87+
if err != nil {
88+
t.Errorf("expected Prepare to succeed, got %v", err)
89+
}
90+
91+
if prep.KvmQemu == nil {
92+
t.Fatalf("expected KvmQemu prepared state, got nil")
93+
}
94+
95+
foundDrive := false
96+
for _, arg := range prep.KvmQemu.CommandArgs {
97+
if strings.Contains(arg, "format=raw") {
98+
foundDrive = true
99+
}
100+
}
101+
if !foundDrive {
102+
t.Errorf("expected format=raw in args, got %v", prep.KvmQemu.CommandArgs)
103+
}
104+
}
105+
106+
func TestCloudHypervisorExecutor_PrepareValidImageTyped(t *testing.T) {
107+
exec := &CloudHypervisorExecutor{}
108+
109+
f, err := os.CreateTemp("", "dummy_img_*.raw")
110+
if err != nil {
111+
t.Fatalf("could not create temp file: %v", err)
112+
}
113+
defer os.Remove(f.Name())
114+
115+
spec := launch.LaunchSpec{
116+
Architecture: "x86_64",
117+
Storage: []launch.StorageAttachmentSpec{
118+
{HostPath: f.Name(), Format: "raw"},
119+
},
120+
Vcpu: 2,
121+
MemoryMB: 1024,
122+
}
123+
124+
prep, err := exec.Prepare(spec)
125+
if err != nil {
126+
t.Errorf("expected Prepare to succeed, got %v", err)
127+
}
128+
129+
if prep.CloudHypervisor == nil {
130+
t.Fatalf("expected CloudHypervisor prepared state, got nil")
131+
}
132+
133+
foundDrive := false
134+
for _, arg := range prep.CloudHypervisor.CommandArgs {
135+
if strings.Contains(arg, "path="+f.Name()) {
136+
foundDrive = true
137+
}
138+
}
139+
if !foundDrive {
140+
t.Errorf("expected path in args, got %v", prep.CloudHypervisor.CommandArgs)
141+
}
142+
}
143+
144+
func TestFirecrackerExecutor_PrepareValidImageTyped(t *testing.T) {
145+
exec := &FirecrackerExecutor{}
146+
147+
spec := launch.LaunchSpec{
148+
Architecture: "x86_64",
149+
Storage: []launch.StorageAttachmentSpec{
150+
{HostPath: "/tmp/rootfs.ext4", Format: "ext4", MountPoint: "/"},
151+
{HostPath: "/tmp/vmlinux", Format: "raw", ReadOnly: true, MountPoint: "/boot/vmlinux"},
152+
},
153+
Vcpu: 2,
154+
MemoryMB: 1024,
155+
}
156+
157+
prep, err := exec.Prepare(spec)
158+
if err != nil {
159+
t.Errorf("expected Prepare to succeed, got %v", err)
160+
}
161+
162+
if prep.Firecracker == nil {
163+
t.Fatalf("expected Firecracker prepared state, got nil")
164+
}
165+
166+
if prep.Firecracker.RootfsPath != "/tmp/rootfs.ext4" {
167+
t.Errorf("expected rootfs path to be set from typed storage")
168+
}
169+
if prep.Firecracker.KernelImagePath != "/tmp/vmlinux" {
170+
t.Errorf("expected kernel image path to be set from typed storage")
57171
}
58172
}
59173

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package runtime
2+
3+
import "github.com/TechnologyTailors/Schedune/schedune-control-plane/pkg/schema/launch"
4+
5+
func resolvePrimaryDisk(spec launch.LaunchSpec) (string, string) {
6+
if len(spec.Storage) > 0 {
7+
for _, s := range spec.Storage {
8+
if s.MountPoint == "/" || s.MountPoint == "" {
9+
format := s.Format
10+
if format == "" {
11+
format = "raw"
12+
}
13+
return s.HostPath, format
14+
}
15+
}
16+
format := spec.Storage[0].Format
17+
if format == "" {
18+
format = "raw"
19+
}
20+
return spec.Storage[0].HostPath, format
21+
}
22+
if spec.ImageReference != "" {
23+
return spec.ImageReference, "qcow2"
24+
}
25+
return "", ""
26+
}
27+
28+
func resolveFirecrackerDisks(spec launch.LaunchSpec) (kernel string, rootfs string) {
29+
if len(spec.Storage) > 0 {
30+
for _, s := range spec.Storage {
31+
if s.MountPoint == "/" {
32+
rootfs = s.HostPath
33+
} else if s.MountPoint == "/boot/vmlinux" || (s.Format == "raw" && s.ReadOnly) {
34+
kernel = s.HostPath
35+
}
36+
}
37+
if kernel != "" && rootfs != "" {
38+
return kernel, rootfs
39+
}
40+
}
41+
return spec.KernelImagePath, spec.RootfsPath
42+
}

0 commit comments

Comments
 (0)