From 31ac1851fb3e8ad8a9f944566615f416b5dd9707 Mon Sep 17 00:00:00 2001 From: Bella Khizgiyaev Date: Tue, 5 Mar 2024 10:14:33 +0200 Subject: [PATCH] OVA: workaround for virt-v2v firmware detection Signed-off-by: Bella Khizgiyaev --- .../forklift.konveyor.io_migrations.yaml | 3 + .../crd/bases/forklift.konveyor.io_plans.yaml | 4 + pkg/apis/forklift/v1beta1/plan/vm.go | 2 + pkg/controller/plan/BUILD.bazel | 1 + pkg/controller/plan/adapter/ova/BUILD.bazel | 2 - pkg/controller/plan/kubevirt.go | 36 +++-- pkg/controller/plan/migration.go | 10 +- pkg/controller/plan/util/BUILD.bazel | 3 + pkg/controller/plan/util/kubevirtvmparser.go | 135 ++++++++++++++++++ 9 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 pkg/controller/plan/util/kubevirtvmparser.go diff --git a/operator/config/crd/bases/forklift.konveyor.io_migrations.yaml b/operator/config/crd/bases/forklift.konveyor.io_migrations.yaml index fc1f38d66..ff0554bc6 100644 --- a/operator/config/crd/bases/forklift.konveyor.io_migrations.yaml +++ b/operator/config/crd/bases/forklift.konveyor.io_migrations.yaml @@ -377,6 +377,9 @@ spec: The VM Namespace Only relevant for an openshift source. type: string + newName: + description: The new name of the VM after matching DNS1123 requirements. + type: string operatingSystem: description: The Operating System detected by virt-v2v. type: string diff --git a/operator/config/crd/bases/forklift.konveyor.io_plans.yaml b/operator/config/crd/bases/forklift.konveyor.io_plans.yaml index 1a47659d9..2c19fb8ca 100644 --- a/operator/config/crd/bases/forklift.konveyor.io_plans.yaml +++ b/operator/config/crd/bases/forklift.konveyor.io_plans.yaml @@ -888,6 +888,10 @@ spec: The VM Namespace Only relevant for an openshift source. type: string + newName: + description: The new name of the VM after matching DNS1123 + requirements. + type: string operatingSystem: description: The Operating System detected by virt-v2v. type: string diff --git a/pkg/apis/forklift/v1beta1/plan/vm.go b/pkg/apis/forklift/v1beta1/plan/vm.go index a92820add..45f5c8b3b 100644 --- a/pkg/apis/forklift/v1beta1/plan/vm.go +++ b/pkg/apis/forklift/v1beta1/plan/vm.go @@ -72,6 +72,8 @@ type VMStatus struct { Firmware string `json:"firmware,omitempty"` // The Operating System detected by virt-v2v. OperatingSystem string `json:"operatingSystem,omitempty"` + // The new name of the VM after matching DNS1123 requirements. + NewName string `json:"newName,omitempty"` // Conditions. libcnd.Conditions `json:",inline"` diff --git a/pkg/controller/plan/BUILD.bazel b/pkg/controller/plan/BUILD.bazel index 251295330..c7a3fbef0 100644 --- a/pkg/controller/plan/BUILD.bazel +++ b/pkg/controller/plan/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//pkg/controller/plan/context", "//pkg/controller/plan/handler", "//pkg/controller/plan/scheduler", + "//pkg/controller/plan/util", "//pkg/controller/provider/web", "//pkg/controller/provider/web/vsphere", "//pkg/controller/validation", diff --git a/pkg/controller/plan/adapter/ova/BUILD.bazel b/pkg/controller/plan/adapter/ova/BUILD.bazel index 7e226422e..0d5368639 100644 --- a/pkg/controller/plan/adapter/ova/BUILD.bazel +++ b/pkg/controller/plan/adapter/ova/BUILD.bazel @@ -7,7 +7,6 @@ go_library( "builder.go", "client.go", "destinationclient.go", - "ovfparser.go", "validator.go", ], importpath = "github.com/konveyor/forklift-controller/pkg/controller/plan/adapter/ova", @@ -27,7 +26,6 @@ go_library( "//pkg/lib/error", "//pkg/lib/inventory/web", "//pkg/lib/itinerary", - "//pkg/lib/logging", "//vendor/github.com/go-logr/logr", "//vendor/k8s.io/api/core/v1:core", "//vendor/k8s.io/apimachinery/pkg/api/resource", diff --git a/pkg/controller/plan/kubevirt.go b/pkg/controller/plan/kubevirt.go index ac45c6954..163741bc0 100644 --- a/pkg/controller/plan/kubevirt.go +++ b/pkg/controller/plan/kubevirt.go @@ -16,6 +16,7 @@ import ( "time" planbase "github.com/konveyor/forklift-controller/pkg/controller/plan/adapter/base" + "github.com/konveyor/forklift-controller/pkg/controller/plan/util" "github.com/konveyor/forklift-controller/pkg/controller/provider/web" model "github.com/konveyor/forklift-controller/pkg/controller/provider/web/vsphere" libref "github.com/konveyor/forklift-controller/pkg/lib/ref" @@ -28,7 +29,6 @@ import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - k8svalidation "k8s.io/apimachinery/pkg/util/validation" cnv "kubevirt.io/api/core/v1" instancetypeapi "kubevirt.io/api/instancetype" instancetype "kubevirt.io/api/instancetype/v1beta1" @@ -38,7 +38,6 @@ import ( "github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1/plan" "github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1/ref" "github.com/konveyor/forklift-controller/pkg/controller/plan/adapter" - ovfparser "github.com/konveyor/forklift-controller/pkg/controller/plan/adapter/ova" inspectionparser "github.com/konveyor/forklift-controller/pkg/controller/plan/adapter/vsphere" plancontext "github.com/konveyor/forklift-controller/pkg/controller/plan/context" libcnd "github.com/konveyor/forklift-controller/pkg/lib/condition" @@ -961,7 +960,8 @@ func (r *KubeVirt) UpdateVmByConvertedConfig(vm *plan.VMStatus, pod *core.Pod, s return nil } - 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. The server will be accessible via virt-v2v only after the command has finished. @@ -980,13 +980,12 @@ func (r *KubeVirt) UpdateVmByConvertedConfig(vm *plan.VMStatus, pod *core.Pod, s switch r.Source.Provider.Type() { case api.Ova: - vmConfigBytes, err := io.ReadAll(resp.Body) + vmConf, err := io.ReadAll(resp.Body) if err != nil { - return liberr.Wrap(err) + return err } - vmConfigXML := string(vmConfigBytes) - if vm.Firmware, err = ovfparser.GetFirmwareFromConfig(vmConfigXML); err != nil { - return liberr.Wrap(err) + if vm.Firmware, err = util.GetFirmwareFromYaml(vmConf); err != nil { + return err } case api.VSphere: inspectionXML, err := r.getInspectionXml(pod) @@ -994,8 +993,9 @@ func (r *KubeVirt) UpdateVmByConvertedConfig(vm *plan.VMStatus, pod *core.Pod, s return err } if vm.OperatingSystem, err = inspectionparser.GetOperationSystemFromConfig(inspectionXML); err != nil { - return liberr.Wrap(err) + return err } + r.Log.Info("Setting the vm OS ", vm.OperatingSystem, "vmId", vm.ID) } shutdownURL := fmt.Sprintf("http://%s:8080/shutdown", pod.Status.PodIP) @@ -1279,13 +1279,10 @@ func (r *KubeVirt) virtualMachine(vm *plan.VMStatus) (object *cnv.VirtualMachine //convention it will be automatically changed. var originalName string - if errs := k8svalidation.IsDNS1123Label(vm.Name); len(errs) > 0 { + if vm.NewName != "" { originalName = vm.Name - vm.Name, err = r.changeVmNameDNS1123(vm.Name, r.Plan.Spec.TargetNamespace) - if err != nil { - r.Log.Error(err, "Failed to update the VM name to meet DNS1123 protocol requirements.") - return - } + vm.Name = vm.NewName + r.Log.Info("VM name is incompatible with DNS1123 RFC, renaming", "originalName", originalName, "newName", vm.Name) } @@ -1743,6 +1740,15 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, Value: vm.RootDisk, }) } + + if vm.NewName != "" { + environment = append(environment, + core.EnvVar{ + Name: "V2V_NewName", + Value: vm.NewName, + }) + } + // pod annotations annotations := map[string]string{} if r.Plan.Spec.TransferNetwork != nil { diff --git a/pkg/controller/plan/migration.go b/pkg/controller/plan/migration.go index 65b042e7a..eaee3b200 100644 --- a/pkg/controller/plan/migration.go +++ b/pkg/controller/plan/migration.go @@ -30,6 +30,7 @@ import ( meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + k8svalidation "k8s.io/apimachinery/pkg/util/validation" cdi "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -718,6 +719,13 @@ func (r *Migration) execute(vm *plan.VMStatus) (err error) { err = nil break } + if errs := k8svalidation.IsDNS1123Label(vm.Name); len(errs) > 0 { + vm.NewName, err = r.kubevirt.changeVmNameDNS1123(vm.Name, r.Plan.Spec.TargetNamespace) + if err != nil { + r.Log.Error(err, "Failed to update the VM name to meet DNS1123 protocol requirements.") + return + } + } vm.Phase = r.next(vm.Phase) case PreHook, PostHook: runner := HookRunner{Context: r.Context} @@ -1141,7 +1149,7 @@ func (r *Migration) execute(vm *plan.VMStatus) (err error) { if pod != nil && pod.Status.Phase != core.PodSucceeded { err := r.kubevirt.UpdateVmByConvertedConfig(vm, pod, step) if err != nil { - return err + return liberr.Wrap(err) } } } diff --git a/pkg/controller/plan/util/BUILD.bazel b/pkg/controller/plan/util/BUILD.bazel index 195f4cb78..dff289312 100644 --- a/pkg/controller/plan/util/BUILD.bazel +++ b/pkg/controller/plan/util/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "util", srcs = [ + "kubevirtvmparser.go", "openstack.go", "ovirt.go", "utils.go", @@ -13,7 +14,9 @@ go_library( "//pkg/apis/forklift/v1beta1", "//pkg/controller/provider/web/openstack", "//pkg/controller/provider/web/ovirt", + "//pkg/lib/logging", "//pkg/settings", + "//vendor/gopkg.in/yaml.v2:yaml_v2", "//vendor/k8s.io/api/core/v1:core", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:meta", ], diff --git a/pkg/controller/plan/util/kubevirtvmparser.go b/pkg/controller/plan/util/kubevirtvmparser.go new file mode 100644 index 000000000..37ab5d95e --- /dev/null +++ b/pkg/controller/plan/util/kubevirtvmparser.go @@ -0,0 +1,135 @@ +package util + +import ( + "fmt" + "strings" + + "github.com/konveyor/forklift-controller/pkg/lib/logging" + "gopkg.in/yaml.v2" +) + +const ( + // Name. + Name = "kubevirt-vm-parser" +) + +// Package logger. +var log = logging.WithName(Name) + +// Map of osinfo ids to vmware guest ids. +var osV2VMap = map[string]string{ + "centos6": "centos6_64Guest", + "centos7": "centos7_64Guest", + "centos8": "centos8_64Guest", + "centos9": "centos9_64Guest", + "rhel7": "rhel7_64Guest", + "rhel8": "rhel8_64Guest", + "rhel9": "rhel9_64Guest", + "rocky": "rockylinux_64Guest", + "sles10": "sles10_64Guest", + "sles11": "sles11_64Guest", + "sles12": "sles12_64Guest", + "sles15": "sles15_64Guest", + "sles16": "sles16_64Guest", + "opensuse": "opensuse64Guest", + "debian4": "debian4_64Guest", + "debian5": "debian5_64Guest", + "debian6": "debian6_64Guest", + "debian7": "debian7_64Guest", + "debian8": "debian8_64Guest", + "debian9": "debian9_64Guest", + "debian10": "debian10_64Guest", + "debian11": "debian11_64Guest", + "debian12": "debian12_64Guest", + "ubuntu": "ubuntu64Guest", + "fedora": "fedora64Guest", + "win7": "windows7Server64Guest", + "win8": "windows8Server64Guest", + "win10": "windows9Server64Guest", + "win11": "windows11_64Guest", + "win12": "windows12_64Guest", + "win2k19": "windows2019srv_64Guest", + "win2k22": "windows2022srvNext_64Guest", +} + +type OS struct { + Firmware string `yaml:"firmware"` +} + +type Domain struct { + OS OS `yaml:"os"` +} + +type TemplateSpec struct { + Domain Domain `yaml:"domain"` +} + +type Template struct { + Spec TemplateSpec `yaml:"spec"` +} + +type VirtualMachineSpec struct { + Template Template `yaml:"template"` +} + +type VirtualMachine struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata Metadata `yaml:"metadata"` + Spec VirtualMachineSpec `yaml:"spec"` +} + +type Metadata struct { + Name string `yaml:"name"` + Labels map[string]string `yaml:"labels"` +} + +func GetFirmwareFromYaml(yamlData []byte) (string, error) { + var vm VirtualMachine + if err := yaml.Unmarshal(yamlData, &vm); err != nil { + return "", err + } + + firmware := vm.Spec.Template.Spec.Domain.OS.Firmware + if firmware == "" { + log.Info("Firmware type was not detected") + } + + return firmware, nil +} + +func GetOperationSystemFromYaml(yamlData []byte) (os string, err error) { + var vm VirtualMachine + if err = yaml.Unmarshal(yamlData, &vm); err != nil { + return + } + + labels := vm.Metadata.Labels + if osinfo, ok := labels["libguestfs.org/osinfo"]; ok { + return mapOs(osinfo), nil + + } + return +} + +func mapOs(labelOS string) (os string) { + distro := strings.SplitN(labelOS, ".", 2)[0] + + switch { + case strings.HasPrefix(distro, "rocky"): + distro = "rocky" + case strings.HasPrefix(distro, "opensuse"): + distro = "opensuse" + case strings.HasPrefix(distro, "ubuntu"): + distro = "ubuntu" + case strings.HasPrefix(distro, "fedora"): + distro = "fedora" + } + + os, ok := osV2VMap[distro] + if !ok { + log.Info(fmt.Sprintf("Received %s, mapped to: %s", labelOS, os)) + os = "otherGuest64" + } + return +}