Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 2f8a243

Browse files
committed
feat: add persistent disks on raveld with ZFS
1 parent 60e1d20 commit 2f8a243

File tree

34 files changed

+963
-40
lines changed

34 files changed

+963
-40
lines changed

cmd/ravel/commands/disks/create.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package disks
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/valyentdev/ravel/cmd/ravel/util"
6+
"github.com/valyentdev/ravel/core/daemon"
7+
)
8+
9+
func newCreateCmd() *cobra.Command {
10+
var size uint64
11+
12+
cmd := &cobra.Command{
13+
Use: "create <id> [--size|-s <size>]",
14+
Short: "Create a disk",
15+
RunE: func(cmd *cobra.Command, args []string) error {
16+
17+
if len(args) == 0 {
18+
cmd.Println("id is required")
19+
return cmd.Usage()
20+
}
21+
return runCreateDisk(cmd, args[0], size)
22+
},
23+
}
24+
25+
cmd.Flags().Uint64VarP(&size, "size", "s", 512, "Size of the disk in MB")
26+
27+
return cmd
28+
}
29+
30+
func runCreateDisk(cmd *cobra.Command, id string, size uint64) error {
31+
disk, err := util.GetDaemonClient(cmd).CreateDisk(cmd.Context(), daemon.DiskOptions{
32+
SizeMB: size,
33+
Id: id,
34+
})
35+
36+
if err != nil {
37+
return err
38+
}
39+
40+
cmd.Printf("Disk %s created\n", disk.Id)
41+
return nil
42+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package disks
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/valyentdev/ravel/cmd/ravel/util"
6+
)
7+
8+
func newDestroyCmd() *cobra.Command {
9+
return &cobra.Command{
10+
Use: "destroy <id>",
11+
Short: "Destroy a disk",
12+
RunE: func(cmd *cobra.Command, args []string) error {
13+
return runDestroyDisk(cmd, args[0])
14+
},
15+
}
16+
}
17+
18+
func runDestroyDisk(cmd *cobra.Command, id string) error {
19+
err := util.GetDaemonClient(cmd).DestroyDisk(cmd.Context(), id)
20+
if err != nil {
21+
return err
22+
}
23+
cmd.Printf("Disk %s destroyed\n", id)
24+
return nil
25+
}

cmd/ravel/commands/disks/disks.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package disks
2+
3+
import "github.com/spf13/cobra"
4+
5+
func NewDisksCmd() *cobra.Command {
6+
cmd := &cobra.Command{
7+
Use: "disks",
8+
Short: "Manage disks",
9+
}
10+
11+
cmd.AddCommand(newCreateCmd())
12+
cmd.AddCommand(newListCmd())
13+
cmd.AddCommand(newDestroyCmd())
14+
15+
return cmd
16+
}

cmd/ravel/commands/disks/ls.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package disks
2+
3+
import (
4+
"fmt"
5+
"text/tabwriter"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/valyentdev/ravel/cmd/ravel/util"
9+
)
10+
11+
func newListCmd() *cobra.Command {
12+
return &cobra.Command{
13+
Use: "ls",
14+
Short: "List disks",
15+
RunE: func(cmd *cobra.Command, args []string) error {
16+
disks, err := util.GetDaemonClient(cmd).ListDisks(cmd.Context())
17+
if err != nil {
18+
return err
19+
}
20+
21+
w := tabwriter.NewWriter(cmd.OutOrStdout(), 1, 1, 1, ' ', 0)
22+
23+
fmt.Fprintln(w, "ID\tINSTANCE\tSIZE MB\tCREATED AT")
24+
for _, disk := range disks {
25+
var instance string
26+
if disk.AttachedInstance != "" {
27+
instance = disk.AttachedInstance
28+
} else {
29+
instance = "-"
30+
}
31+
32+
fmt.Fprintf(w, "%s\t%s\t%dMB\t%s\n", disk.Id, instance, disk.SizeMB, disk.CreatedAt)
33+
}
34+
w.Flush()
35+
36+
return nil
37+
},
38+
}
39+
}

cmd/ravel/commands/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/valyentdev/ravel/cmd/ravel/commands/corrosion"
88
"github.com/valyentdev/ravel/cmd/ravel/commands/daemon"
99
"github.com/valyentdev/ravel/cmd/ravel/commands/db"
10+
"github.com/valyentdev/ravel/cmd/ravel/commands/disks"
1011
"github.com/valyentdev/ravel/cmd/ravel/commands/images"
1112
"github.com/valyentdev/ravel/cmd/ravel/commands/instance"
1213
"github.com/valyentdev/ravel/cmd/ravel/commands/server"
@@ -39,6 +40,7 @@ func NewRootCmd() *cobra.Command {
3940
images.NewImagesCmd(),
4041
tls.NewTLSCommand(),
4142
corrosion.NewCorroCmd(),
43+
disks.NewDisksCmd(),
4244
)
4345

4446
return rootCmd

core/config/runtime.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ type RuntimeConfig struct {
55
JailerBinary string `json:"jailer_binary" toml:"jailer_binary"`
66
InitBinary string `json:"init_binary" toml:"init_binary"`
77
LinuxKernel string `json:"linux_kernel" toml:"linux_kernel"`
8+
ZFSPool string `json:"zfs_pool" toml:"zfs_pool"`
89
}

core/daemon/daemon.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/valyentdev/ravel/api/errdefs"
1010
"github.com/valyentdev/ravel/core/instance"
1111
"github.com/valyentdev/ravel/core/validation"
12+
"github.com/valyentdev/ravel/runtime/disks"
1213
)
1314

1415
type InstanceOptions struct {
@@ -34,6 +35,11 @@ func (i *InstanceOptions) Validate() error {
3435
return nil
3536
}
3637

38+
type DiskOptions struct {
39+
Id string `json:"id"`
40+
SizeMB uint64 `json:"size_mb"`
41+
}
42+
3743
type Daemon interface {
3844
CreateInstance(ctx context.Context, opt InstanceOptions) (*instance.Instance, error)
3945
GetInstance(ctx context.Context, id string) (*instance.Instance, error)
@@ -48,4 +54,9 @@ type Daemon interface {
4854
DeleteImage(ctx context.Context, ref string) error
4955
ListImages(ctx context.Context) ([]images.Image, error)
5056
PullImage(ctx context.Context, opt ImagePullOptions) (*images.Image, error)
57+
58+
CreateDisk(ctx context.Context, opt DiskOptions) (*disks.Disk, error)
59+
GetDisk(ctx context.Context, id string) (*disks.Disk, error)
60+
ListDisks(ctx context.Context) ([]disks.Disk, error)
61+
DestroyDisk(ctx context.Context, id string) error
5162
}

core/instance/instance.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,26 @@ type State struct {
4545
ExitResult *ExitResult `json:"exit_result,omitempty"`
4646
}
4747

48+
type Mount struct {
49+
Disk string `json:"disk"`
50+
Path string `json:"path"`
51+
}
52+
4853
type InstanceConfig struct {
49-
Image string `json:"image"`
50-
Guest InstanceGuestConfig `json:"guest"`
51-
Init api.InitConfig `json:"init"`
52-
Stop *api.StopConfig `json:"stop,omitempty"`
53-
Env []string `json:"env"`
54+
Image string `json:"image"`
55+
Guest InstanceGuestConfig `json:"guest"`
56+
Init api.InitConfig `json:"init"`
57+
Stop *api.StopConfig `json:"stop,omitempty"`
58+
Env []string `json:"env,omitempty"`
59+
Mounts []Mount `json:"mounts,omitempty"`
60+
}
61+
62+
func (ic InstanceConfig) GetDisks() []string {
63+
disks := make([]string, len(ic.Mounts))
64+
for i, m := range ic.Mounts {
65+
disks[i] = m.Disk
66+
}
67+
return disks
5468
}
5569

5670
type InstanceGuestConfig struct {

core/validation/instance.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ import (
1212

1313
var idRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
1414

15+
func ValidateObjectId(id string) error {
16+
if id == "" {
17+
return errors.New("id is required")
18+
}
19+
20+
if len(id) > 64 {
21+
return errors.New("id is too long")
22+
}
23+
24+
if !idRegexp.MatchString(id) {
25+
return errors.New("id contains invalid characters")
26+
}
27+
return nil
28+
}
29+
1530
func ValidateInstanceId(id string) error {
1631
if id == "" {
1732
return errors.New("instance id is required")

docs/examples/example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
},
1414
"init": {
1515
"user": "root"
16-
}
16+
},
17+
"auto_destroy": true
1718
},
1819
"guest": {
1920
"cpu_kind": "eco",

0 commit comments

Comments
 (0)