package mapper import ( "fmt" "reflect" corev1 "k8s.io/api/core/v1" kr "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" virtv1 "kubevirt.io/api/core/v1" virtbeta "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "edge-infra.dev/pkg/edge/api/graph/model" nodemeta "edge-infra.dev/pkg/sds/ien/node" ) var ( storageTypeVM = "local-path" interfaceModelVM = "e1000" nodeAffinityKeyVM = nodemeta.ClassLabel nodeAffinityValuesVM = []string{model.TerminalClassTypeTouchpoint.String()} ) func VirtualMachineToKubeVirtualMachine(virtualMachine *model.VirtualMachine) (virtv1.VirtualMachine, error) { return virtualMachineToKubeVirtualMachine(virtualMachine) } func virtualMachineToKubeVirtualMachine(virtualMachine *model.VirtualMachine) (virtv1.VirtualMachine, error) { virtualMachineSpec, err := getVirtualMachineSpec(virtualMachine) if err != nil { return virtv1.VirtualMachine{}, err } kubeVM := virtv1.VirtualMachine{ ObjectMeta: metav1.ObjectMeta{ Name: virtualMachine.Hostname, Namespace: virtualMachine.Namespace, Annotations: map[string]string{ "linkerd.io/inject": "disabled", }, }, Spec: virtualMachineSpec, Status: virtv1.VirtualMachineStatus{}, } kubeVM.SetGroupVersionKind(schema.GroupVersionKind{ Group: virtv1.GroupVersion.Group, Kind: reflect.TypeOf(virtv1.VirtualMachine{}).Name(), Version: virtv1.GroupVersion.Version, }) return kubeVM, nil } func getKubeVMInstanceSpec(virtualMachine *model.VirtualMachine) (virtv1.VirtualMachineInstanceSpec, error) { memoryQuantity, err := kr.ParseQuantity(virtualMachine.Memory) if err != nil { return virtv1.VirtualMachineInstanceSpec{}, err } return virtv1.VirtualMachineInstanceSpec{ Domain: virtv1.DomainSpec{ CPU: &virtv1.CPU{ Cores: uint32(virtualMachine.Cpus), /* #nosec G115 */ }, Memory: &virtv1.Memory{ Guest: &memoryQuantity, }, Devices: virtv1.Devices{ ClientPassthrough: &virtv1.ClientPassthroughDevices{}, Disks: virtualMachineDisksToKubeDisks(virtualMachine.Disks, virtualMachine.Hostname), Interfaces: []virtv1.Interface{ { Model: interfaceModelVM, Name: "default", InterfaceBindingMethod: virtv1.InterfaceBindingMethod{ Masquerade: &virtv1.InterfaceMasquerade{}, }, }, }, }, Machine: &virtv1.Machine{ Type: string(virtualMachine.MachineType), }, }, Networks: []virtv1.Network{ { Name: "default", NetworkSource: virtv1.NetworkSource{ Pod: &virtv1.PodNetwork{}, }, }, }, Volumes: virtualMachineDisksToKubeVolumes(virtualMachine.Disks, virtualMachine.Hostname), Affinity: getVirtualMachineAffinity(), }, nil } func virtualMachineDisksToKubeDisks(disks []*model.VirtualMachineDisk, hostname string) []virtv1.Disk { kubeDisks := []virtv1.Disk{} for _, disk := range disks { bootOrder := uint(disk.BootOrder) /* #nosec G115 */ kubeDisk := virtv1.Disk{ BootOrder: &bootOrder, Name: getKubeDiskName(hostname, disk.DiskID), DiskDevice: getKubeDiskDevice(disk), } kubeDisks = append(kubeDisks, kubeDisk) } return kubeDisks } func virtualMachineDisksToKubeVolumes(disks []*model.VirtualMachineDisk, hostname string) []virtv1.Volume { kubeVolumes := []virtv1.Volume{} for _, disk := range disks { volumeName := getKubeDiskName(hostname, disk.DiskID) kubeVolume := virtv1.Volume{ Name: volumeName, VolumeSource: virtv1.VolumeSource{ DataVolume: &virtv1.DataVolumeSource{ Name: volumeName, }, }, } kubeVolumes = append(kubeVolumes, kubeVolume) } return kubeVolumes } func getKubeDiskName(hostname string, diskID string) string { return fmt.Sprintf("%s-%s", hostname, diskID) } func getKubeDiskDevice(disk *model.VirtualMachineDisk) virtv1.DiskDevice { diskDevice := virtv1.DiskDevice{} diskBus := virtv1.DiskBus(disk.Bus) switch disk.Type { case model.DiskTypeCdrom: diskDevice.CDRom = &virtv1.CDRomTarget{ Bus: diskBus, } case model.DiskTypeDisk: diskDevice.Disk = &virtv1.DiskTarget{ Bus: diskBus, } } return diskDevice } func getKubeDataVolumeSource(containerImageURL string) *virtbeta.DataVolumeSource { if containerImageURL == "" { return &virtbeta.DataVolumeSource{ Blank: &virtbeta.DataVolumeBlankImage{}, } } pullMethod := virtbeta.RegistryPullNode return &virtbeta.DataVolumeSource{ Registry: &virtbeta.DataVolumeSourceRegistry{ URL: &containerImageURL, PullMethod: &pullMethod, }, } } func getKubeVMDataVolumeTemplates(virtualMachine *model.VirtualMachine) ([]virtv1.DataVolumeTemplateSpec, error) { storageClassName := storageTypeVM volumeTemplates := []virtv1.DataVolumeTemplateSpec{} for _, disk := range virtualMachine.Disks { sizeQuantity, err := kr.ParseQuantity(disk.Size) if err != nil { return []virtv1.DataVolumeTemplateSpec{}, err } volumeTemplate := virtv1.DataVolumeTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: getKubeDiskName(virtualMachine.Hostname, disk.DiskID), Annotations: map[string]string{ "linkerd.io/inject": "disabled", }, }, Spec: virtbeta.DataVolumeSpec{ PVC: &corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ corev1.ReadWriteOnce, }, StorageClassName: &storageClassName, Resources: corev1.VolumeResourceRequirements{ Requests: map[corev1.ResourceName]kr.Quantity{ corev1.ResourceStorage: sizeQuantity, }, }, }, Source: getKubeDataVolumeSource(disk.ContainerImageURL), }, } volumeTemplates = append(volumeTemplates, volumeTemplate) } return volumeTemplates, nil } func getVirtualMachineSpec(virtualMachine *model.VirtualMachine) (virtv1.VirtualMachineSpec, error) { kubeVMInstanceSpec, err := getKubeVMInstanceSpec(virtualMachine) if err != nil { return virtv1.VirtualMachineSpec{}, err } dataVolumeTemplates, err := getKubeVMDataVolumeTemplates(virtualMachine) if err != nil { return virtv1.VirtualMachineSpec{}, err } return virtv1.VirtualMachineSpec{ Running: &virtualMachine.TargetPowerState, Template: &virtv1.VirtualMachineInstanceTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "kubevirt.io/domain": virtualMachine.Hostname, }, Annotations: map[string]string{ "linkerd.io/inject": "disabled", }, }, Spec: kubeVMInstanceSpec, }, DataVolumeTemplates: dataVolumeTemplates, }, nil } func getVirtualMachineAffinity() *corev1.Affinity { return &corev1.Affinity{ NodeAffinity: &corev1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { MatchExpressions: []corev1.NodeSelectorRequirement{ { Key: nodeAffinityKeyVM, Operator: corev1.NodeSelectorOpIn, Values: nodeAffinityValuesVM, }, }, }, }, }, }, } }