diff --git a/pkg/unikontainers/unikernels/rumprun.go b/pkg/unikontainers/unikernels/rumprun.go index 3925823f1..14a1aaaa2 100644 --- a/pkg/unikontainers/unikernels/rumprun.go +++ b/pkg/unikontainers/unikernels/rumprun.go @@ -31,6 +31,7 @@ type Rumprun struct { Envs []string Net RumprunNet Blk RumprunBlk + Block []types.BlockDevParams } type RumprunCmd struct { @@ -149,22 +150,39 @@ func (r *Rumprun) MonitorNetCli(ifName string, mac string) string { } func (r *Rumprun) MonitorBlockCli() []types.MonitorBlockArgs { + if len(r.Block) == 0 { + if r.Blk.HostPath != "" { + return []types.MonitorBlockArgs{ + { + ID: "rootfs", + Path: r.Blk.HostPath, + }, + } + } + return nil + } switch r.Monitor { case "hvt", "spt": - // TODO: Explore options for multiple block devices in Rumprun - // over Solo5-spt and Solo5-hvt. Solo5 expects to use as an ID - // a specific name which the guest is also aware of in order to - // attach the respective block. As a result, urunc needs to know - // the correct ID to set, which is not straightforward. Therefore, - // there are two options. Either we read the Solo5 manifest or, - // we require specific IDs. Till we decide about that, we will - // use a single block device only for the rootfs of Rumprun. - return []types.MonitorBlockArgs{ - { - ID: "rootfs", - Path: r.Blk.HostPath, - }, + blkArgs := make([]types.MonitorBlockArgs, 0, len(r.Block)) + for i, block := range r.Block { + id := block.ID + if id == "" { + if i == 0 { + id = "rootfs" + } else { + id = fmt.Sprintf("vol%d", i) + } + } + path := block.Source + if path == "" && i == 0 { + path = r.Blk.HostPath + } + blkArgs = append(blkArgs, types.MonitorBlockArgs{ + ID: id, + Path: path, + }) } + return blkArgs default: return nil } @@ -218,6 +236,7 @@ func (r *Rumprun) Init(data types.UnikernelParams) error { r.Command = strings.Join(data.CmdLine, " ") r.Monitor = data.Monitor r.Envs = data.EnvVars + r.Block = data.Block return nil } diff --git a/pkg/unikontainers/unikernels/rumprun_test.go b/pkg/unikontainers/unikernels/rumprun_test.go new file mode 100644 index 000000000..cecf43c7e --- /dev/null +++ b/pkg/unikontainers/unikernels/rumprun_test.go @@ -0,0 +1,125 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unikernels + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +func TestRumprunMonitorBlockCli(t *testing.T) { + t.Run("returns nil when no block devices exist", func(t *testing.T) { + r := &Rumprun{} + err := r.Init(types.UnikernelParams{ + Monitor: "hvt", + Block: []types.BlockDevParams{}, + }) + assert.NoError(t, err) + blkArgs := r.MonitorBlockCli() + assert.Nil(t, blkArgs) + }) + + t.Run("returns single block device with default ID rootfs", func(t *testing.T) { + r := &Rumprun{} + err := r.Init(types.UnikernelParams{ + Monitor: "hvt", + Block: []types.BlockDevParams{ + { + Source: "/var/lib/urunc/rootfs.img", + MountPoint: "/data", + }, + }, + }) + assert.NoError(t, err) + blkArgs := r.MonitorBlockCli() + assert.Len(t, blkArgs, 1) + assert.Equal(t, "rootfs", blkArgs[0].ID) + assert.Equal(t, "/var/lib/urunc/rootfs.img", blkArgs[0].Path) + }) + + t.Run("returns multiple block devices with generated IDs", func(t *testing.T) { + r := &Rumprun{} + err := r.Init(types.UnikernelParams{ + Monitor: "spt", + Block: []types.BlockDevParams{ + { + Source: "/var/lib/urunc/rootfs.img", + MountPoint: "/data", + }, + { + Source: "/var/lib/urunc/vol1.img", + MountPoint: "/mnt/vol1", + }, + { + Source: "/var/lib/urunc/vol2.img", + MountPoint: "/mnt/vol2", + }, + }, + }) + assert.NoError(t, err) + blkArgs := r.MonitorBlockCli() + assert.Len(t, blkArgs, 3) + assert.Equal(t, "rootfs", blkArgs[0].ID) + assert.Equal(t, "/var/lib/urunc/rootfs.img", blkArgs[0].Path) + assert.Equal(t, "vol1", blkArgs[1].ID) + assert.Equal(t, "/var/lib/urunc/vol1.img", blkArgs[1].Path) + assert.Equal(t, "vol2", blkArgs[2].ID) + assert.Equal(t, "/var/lib/urunc/vol2.img", blkArgs[2].Path) + }) + + t.Run("preserves explicit block IDs", func(t *testing.T) { + r := &Rumprun{} + err := r.Init(types.UnikernelParams{ + Monitor: "hvt", + Block: []types.BlockDevParams{ + { + ID: "custom_root", + Source: "/path/to/root.img", + MountPoint: "/data", + }, + { + ID: "db_storage", + Source: "/path/to/db.img", + MountPoint: "/db", + }, + }, + }) + assert.NoError(t, err) + blkArgs := r.MonitorBlockCli() + assert.Len(t, blkArgs, 2) + assert.Equal(t, "custom_root", blkArgs[0].ID) + assert.Equal(t, "/path/to/root.img", blkArgs[0].Path) + assert.Equal(t, "db_storage", blkArgs[1].ID) + assert.Equal(t, "/path/to/db.img", blkArgs[1].Path) + }) + + t.Run("returns nil for non-Solo5 monitors", func(t *testing.T) { + r := &Rumprun{} + err := r.Init(types.UnikernelParams{ + Monitor: "qemu", + Block: []types.BlockDevParams{ + { + Source: "/path/to/root.img", + MountPoint: "/data", + }, + }, + }) + assert.NoError(t, err) + blkArgs := r.MonitorBlockCli() + assert.Nil(t, blkArgs) + }) +}