Skip to content

Commit

Permalink
OVA: workaround for consuming firmware from virt-v2v
Browse files Browse the repository at this point in the history
Signed-off-by: Bella Khizgiyaev <[email protected]>
  • Loading branch information
bkhizgiy committed Mar 12, 2024
1 parent de364a1 commit ae3f78f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 34 deletions.
38 changes: 12 additions & 26 deletions pkg/controller/plan/adapter/ova/ovfparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,21 @@ package ova
import (
"encoding/xml"
"strings"

liberr "github.com/konveyor/forklift-controller/pkg/lib/error"
)

type OvaVmconfig struct {
XMLName xml.Name `xml:"domain"`
Name string `xml:"name"`
OS OS `xml:"os"`
}

type OS struct {
Type OSType `xml:"type"`
Loader Loader `xml:"loader"`
Nvram Nvram `xml:"nvram"`
}

type OSType struct {
Arch string `xml:"arch,attr"`
Machine string `xml:"machine,attr"`
Content string `xml:",chardata"`
XMLName xml.Name `xml:"domain"`
Firmware Firmware `xml:"firmware"`
}

type Loader struct {
Readonly string `xml:"readonly,attr"`
Type string `xml:"type,attr"`
Secure string `xml:"secure,attr"`
Path string `xml:",chardata"`
type Firmware struct {
Bootloader Bootloader `xml:"bootloader"`
}

type Nvram struct {
Template string `xml:"template,attr"`
type Bootloader struct {
Type string `xml:"type,attr"`
}

func readConfFromXML(xmlData string) (*OvaVmconfig, error) {
Expand All @@ -54,9 +40,9 @@ func GetFirmwareFromConfig(vmConfigXML string) (firmware string, err error) {
return
}

path := xmlConf.OS.Loader.Path
if strings.Contains(path, "OVMF") {
return UEFI, nil
firmware = xmlConf.Firmware.Bootloader.Type
if firmware == "" {
err = liberr.New("failed to get the firmware type from virt-v2v config")
}
return BIOS, nil
return
}
4 changes: 3 additions & 1 deletion pkg/controller/plan/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ func (r *KubeVirt) UpdateVmByConvertedConfig(vm *plan.VMStatus, pod *core.Pod, s
return
}

url := fmt.Sprintf("http://%s:8080/ovf", pod.Status.PodIP)
url := fmt.Sprintf("http://%s:8080/vm", pod.Status.PodIP)

/* Due to the virt-v2v operation, the ovf file is only available after the command's execution,
meaning it appears following the copydisks phase.
Expand Down Expand Up @@ -941,6 +941,8 @@ func (r *KubeVirt) UpdateVmByConvertedConfig(vm *plan.VMStatus, pod *core.Pod, s
return
}

r.Log.Info("setting the vm firmware to ", firmware, "vmId", vm.ID)

vm.Firmware = firmware

shutdownURL := fmt.Sprintf("http://%s:8080/shutdown", pod.Status.PodIP)
Expand Down
119 changes: 112 additions & 7 deletions virt-v2v/cold/entrypoint.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package main

import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
)

const (
Expand All @@ -25,6 +28,9 @@ var (
server *http.Server
)

var UEFI_RE = regexp.MustCompile(`(?i)UEFI\s+bootloader?`)
var firmware = "bios"

func main() {
virtV2vArgs := []string{"virt-v2v", "-v", "-x"}
source := os.Getenv("V2V_source")
Expand Down Expand Up @@ -97,7 +103,7 @@ func main() {
virtV2vArgs = append(virtV2vArgs, "--", os.Getenv("V2V_vmName"))
}

if err := executeVirtV2v(virtV2vArgs); err != nil {
if err := executeVirtV2v(source, virtV2vArgs); err != nil {
fmt.Println("Error executing virt-v2v command ", err)
os.Exit(1)
}
Expand All @@ -110,7 +116,7 @@ func main() {
os.Exit(1)
}

http.HandleFunc("/ovf", ovfHandler)
http.HandleFunc("/vm", vmHandler)
http.HandleFunc("/shutdown", shutdownHandler)
server = &http.Server{Addr: ":8080"}

Expand Down Expand Up @@ -169,16 +175,26 @@ func LinkDisks(diskKind string, num int) (err error) {
return
}

func executeVirtV2v(args []string) (err error) {
func executeVirtV2v(source string, args []string) (err error) {
virtV2vCmd := exec.Command(args[0], args[1:]...)
virtV2vStdoutPipe, err := virtV2vCmd.StdoutPipe()
if err != nil {
fmt.Printf("Error setting up stdout pipe: %v\n", err)
return
}
teeOut := io.TeeReader(virtV2vStdoutPipe, os.Stdout)

tee := io.TeeReader(virtV2vStdoutPipe, os.Stdout)
virtV2vCmd.Stderr = os.Stderr
var teeErr io.Reader
if source == OVA {
virtV2vStderrPipe, err := virtV2vCmd.StderrPipe()
if err != nil {
fmt.Printf("Error setting up stdout pipe: %v\n", err)
return err
}
teeErr = io.TeeReader(virtV2vStderrPipe, os.Stderr)
} else {
virtV2vCmd.Stderr = os.Stderr
}

fmt.Println("exec ", virtV2vCmd)
if err = virtV2vCmd.Start(); err != nil {
Expand All @@ -187,7 +203,7 @@ func executeVirtV2v(args []string) (err error) {
}

virtV2vMonitorCmd := exec.Command("/usr/local/bin/virt-v2v-monitor")
virtV2vMonitorCmd.Stdin = tee
virtV2vMonitorCmd.Stdin = teeOut
virtV2vMonitorCmd.Stdout = os.Stdout
virtV2vMonitorCmd.Stderr = os.Stderr

Expand All @@ -196,6 +212,26 @@ func executeVirtV2v(args []string) (err error) {
return
}

if source == OVA {
scanner := bufio.NewScanner(teeErr)
const maxCapacity = 1024 * 1024
buf := make([]byte, 0, 64*1024)
scanner.Buffer(buf, maxCapacity)

for scanner.Scan() {
line := scanner.Bytes()
if match := UEFI_RE.FindSubmatch(line); match != nil {
fmt.Println("UEFI firmware detected")
firmware = "efi"
}
}

if err = scanner.Err(); err != nil {
fmt.Println("Output query failed:", err)
return err
}
}

if err = virtV2vCmd.Wait(); err != nil {
fmt.Printf("Error waiting for virt-v2v to finish: %v\n", err)
return
Expand All @@ -214,13 +250,19 @@ func getXMLFile(dir, fileExtension string) (string, error) {
return "", fmt.Errorf("XML file was not found.")
}

func ovfHandler(w http.ResponseWriter, r *http.Request) {
func vmHandler(w http.ResponseWriter, r *http.Request) {
if xmlFilePath == "" {
fmt.Println("Error: XML file path is empty.")
http.Error(w, "XML file path is empty", http.StatusInternalServerError)
return
}

if err := addFirmwareToXml(xmlFilePath); err != nil {
fmt.Println("Error setting the firmware configuration in the ovf ", err)
http.Error(w, "Error setting the firmware configuration in the ovf", http.StatusInternalServerError)
return
}

xmlData, err := os.ReadFile(xmlFilePath)
if err != nil {
fmt.Printf("Error reading XML file: %v\n", err)
Expand Down Expand Up @@ -255,3 +297,66 @@ func isValidSource(source string) bool {
return false
}
}

func addFirmwareToXml(filePath string) (err error) {
var newFirmwareData string
if firmware == "bios" {
newFirmwareData = (` <firmware>
<bootloader type='bios'/>
</firmware>`)
} else {
newFirmwareData = (` <firmware>
<bootloader type='efi'/>
</firmware>`)
}

file, err := os.Open(filePath)
if err != nil {
return
}
defer file.Close()

tempFilePath := filePath + ".tmp"

tempFile, err := os.Create(tempFilePath)
if err != nil {
return
}
defer tempFile.Close()

scanner := bufio.NewScanner(file)
domainFound := false

for scanner.Scan() {
line := scanner.Text()

if strings.Contains(line, "</os>") {
domainFound = true
}

_, err = tempFile.WriteString(line + "\n")
if err != nil {
return
}

if domainFound {
_, err = tempFile.WriteString(newFirmwareData + "\n")
if err != nil {
return
}
domainFound = false
}
}

if err = scanner.Err(); err != nil {
return
}

err = os.Rename(tempFilePath, filePath)
if err != nil {
return
}

fmt.Println("XML file has been modified successfully.")
return
}

0 comments on commit ae3f78f

Please sign in to comment.