diff --git a/pkg/unikontainers/types/types.go b/pkg/unikontainers/types/types.go index 85b1b49c..0ffc3fa0 100644 --- a/pkg/unikontainers/types/types.go +++ b/pkg/unikontainers/types/types.go @@ -69,15 +69,17 @@ type ProcessConfig struct { // UnikernelParams holds the data required to build the unikernels commandline type UnikernelParams struct { - CmdLine []string // The cmdline provided by the image - EnvVars []string // The environment variables provided by the image - Monitor string // The monitor where guest will execute - Version string // The version of the unikernel - InitrdPath string // The path to the initrd of the unikernel - Net NetDevParams - Block []BlockDevParams - Rootfs RootfsParams // Information about rootfs - ProcConf ProcessConfig // Information for the process execution inside the guest + CmdLine []string // The cmdline provided by the image + EnvVars []string // The environment variables provided by the image + Monitor string // The monitor where guest will execute + Version string // The version of the unikernel + InitrdPath string // The path to the initrd of the unikernel + Net NetDevParams + Block []BlockDevParams + Rootfs RootfsParams // Information about rootfs + ProcConf ProcessConfig // Information for the process execution inside the guest + UnikernelPath string + Annotations map[string]string } // ExecArgs holds the data required by Execve to start the VMM diff --git a/pkg/unikontainers/unikernels/mirage.go b/pkg/unikontainers/unikernels/mirage.go index badfa955..f94bcebd 100644 --- a/pkg/unikontainers/unikernels/mirage.go +++ b/pkg/unikontainers/unikernels/mirage.go @@ -15,19 +15,37 @@ package unikernels import ( + "debug/elf" + "encoding/json" "fmt" "strings" "github.com/urunc-dev/urunc/pkg/unikontainers/types" ) -const MirageUnikernel string = "mirage" +const ( + MirageUnikernel string = "mirage" + AnnotationNetMap = "urunc.dev/mirage-net-map" + AnnotationBlockMap = "urunc.dev/mirage-block-map" +) type Mirage struct { - Command string - Monitor string - Net MirageNet - Block []MirageBlock + Command string + Monitor string + Net MirageNet + Block []MirageBlock + BinaryPath string + Manifest *Solo5Manifest + Annotations map[string]string +} + +type Solo5Manifest struct { + Devices []Solo5Device `json:"devices"` +} + +type Solo5Device struct { + Name string `json:"name"` + Type string `json:"type"` } type MirageNet struct { @@ -57,8 +75,10 @@ func (m *Mirage) SupportsFS(_ string) bool { func (m *Mirage) MonitorNetCli(ifName string, mac string) string { switch m.Monitor { case "hvt", "spt": - netOption := "--net:service=" + ifName - netOption += " --net-mac:service=" + mac + mirageID := m.getMirageDeviceName(ifName, "NET_BASIC", "service") + + netOption := fmt.Sprintf("--net:%s=%s", mirageID, ifName) + netOption += fmt.Sprintf(" --net-mac:%s=%s", mirageID, mac) return netOption default: return "" @@ -81,12 +101,22 @@ func (m *Mirage) MonitorBlockCli() []types.MonitorBlockArgs { // use a single block device. We also need to find some use cases // where multiple block devices are configured in MirageOS and check // how MirageOS handles/configures them. - return []types.MonitorBlockArgs{ - { - ID: "storage", - Path: m.Block[0].HostPath, - }, + var blockArgs []types.MonitorBlockArgs + + for i, blk := range m.Block { + defaultName := "storage" + if i > 0 { + defaultName = fmt.Sprintf("storage%d", i) + } + + mirageID := m.getMirageDeviceName(blk.ID, "BLOCK_BASIC", defaultName) + + blockArgs = append(blockArgs, types.MonitorBlockArgs{ + ID: mirageID, + Path: blk.HostPath, + }) } + return blockArgs default: return nil } @@ -96,8 +126,82 @@ func (m *Mirage) MonitorCli() types.MonitorCliArgs { return types.MonitorCliArgs{} } +// getMirageDeviceName determines the correct Solo5 interface name. +func (m *Mirage) getMirageDeviceName(hostDev string, devType string, defaultName string) string { + // 1. Check Annotations + var mapKey string + if devType == "NET_BASIC" { + mapKey = AnnotationNetMap + } else { + mapKey = AnnotationBlockMap + } + + if val, ok := m.Annotations[mapKey]; ok { + var mapping map[string]string + if err := json.Unmarshal([]byte(val), &mapping); err == nil { + if mirageName, found := mapping[hostDev]; found { + return mirageName + } + } + } + + // 2. Check Manifest (Auto-detect if only one device exists) + if m.Manifest != nil { + var validDevs []string + for _, dev := range m.Manifest.Devices { + if dev.Type == devType { + validDevs = append(validDevs, dev.Name) + } + } + if len(validDevs) == 1 { + return validDevs[0] + } + } + + // 3. Fallback + return defaultName +} + +// parseSolo5Manifest extracts the .note.solo5.manifest section from the ELF +func getSolo5Manifest(path string) (*Solo5Manifest, error) { + f, err := elf.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + section := f.Section(".note.solo5.manifest") + if section == nil { + return nil, fmt.Errorf("no solo5 manifest section found") + } + + data, err := section.Data() + if err != nil { + return nil, err + } + + var manifest Solo5Manifest + // Attempt to find the start of the JSON object '{' + jsonStart := strings.Index(string(data), "{") + if jsonStart == -1 { + return nil, fmt.Errorf("invalid manifest format") + } + + if err := json.Unmarshal(data[jsonStart:], &manifest); err != nil { + return nil, err + } + + return &manifest, nil +} + func (m *Mirage) Init(data types.UnikernelParams) error { - // if Mask is empty, there is no network support + + m.BinaryPath = data.UnikernelPath + m.Annotations = data.Annotations + if manifest, err := getSolo5Manifest(m.BinaryPath); err == nil { + m.Manifest = manifest + } + if data.Net.Mask != "" { m.Net.Address = "--ipv4=" + data.Net.IP + "/24" m.Net.Gateway = "--ipv4-gateway=" + data.Net.Gateway diff --git a/pkg/unikontainers/unikontainers.go b/pkg/unikontainers/unikontainers.go index 7d4136b8..8726a071 100644 --- a/pkg/unikontainers/unikontainers.go +++ b/pkg/unikontainers/unikontainers.go @@ -283,11 +283,13 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { // UnikernelParams // populate unikernel params unikernelParams := types.UnikernelParams{ - CmdLine: u.Spec.Process.Args, - EnvVars: u.Spec.Process.Env, - Monitor: vmmType, - Version: unikernelVersion, - ProcConf: procAttrs, + CmdLine: u.Spec.Process.Args, + EnvVars: u.Spec.Process.Env, + Monitor: vmmType, + Version: unikernelVersion, + ProcConf: procAttrs, + UnikernelPath: u.Unikernel, + Annotations: u.Spec.Annotations, } if len(unikernelParams.CmdLine) == 0 { unikernelParams.CmdLine = strings.Fields(u.State.Annotations[annotCmdLine])