From 520baedfc710a2ac3925c1fce0f1b8c59057b828 Mon Sep 17 00:00:00 2001 From: Liran Rotenberg Date: Tue, 26 Sep 2023 10:55:56 +0300 Subject: [PATCH] Refactoring the OVA NFS Signed-off-by: Liran Rotenberg --- pkg/controller/plan/kubevirt.go | 157 ++++++++++++++++++++------- pkg/controller/provider/ova-setup.go | 133 +++++++++++++---------- 2 files changed, 191 insertions(+), 99 deletions(-) diff --git a/pkg/controller/plan/kubevirt.go b/pkg/controller/plan/kubevirt.go index 3656928af..b0dc61945 100644 --- a/pkg/controller/plan/kubevirt.go +++ b/pkg/controller/plan/kubevirt.go @@ -32,7 +32,6 @@ import ( libcnd "github.com/konveyor/forklift-controller/pkg/lib/condition" liberr "github.com/konveyor/forklift-controller/pkg/lib/error" core "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -1185,7 +1184,10 @@ func (r *KubeVirt) findTemplate(vm *plan.VMStatus) (tmpl *template.Template, err } func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, configMap *core.ConfigMap, pvcs *[]core.PersistentVolumeClaim, v2vSecret *core.Secret) (pod *core.Pod, err error) { - volumes, volumeMounts, volumeDevices := r.podVolumeMounts(vmVolumes, configMap, pvcs) + volumes, volumeMounts, volumeDevices, err := r.podVolumeMounts(vmVolumes, configMap, pvcs) + if err != nil { + return + } // qemu group fsGroup := qemuGroup @@ -1291,7 +1293,7 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, return } -func (r *KubeVirt) podVolumeMounts(vmVolumes []cnv.Volume, configMap *core.ConfigMap, pvcs *[]core.PersistentVolumeClaim) (volumes []core.Volume, mounts []core.VolumeMount, devices []core.VolumeDevice) { +func (r *KubeVirt) podVolumeMounts(vmVolumes []cnv.Volume, configMap *core.ConfigMap, pvcs *[]core.PersistentVolumeClaim) (volumes []core.Volume, mounts []core.VolumeMount, devices []core.VolumeDevice, err error) { pvcsByName := make(map[string]core.PersistentVolumeClaim) for _, pvc := range *pvcs { pvcsByName[pvc.Name] = pvc @@ -1337,18 +1339,32 @@ func (r *KubeVirt) podVolumeMounts(vmVolumes []cnv.Volume, configMap *core.Confi switch r.Source.Provider.Type() { case api.Ova: - pvcName := fmt.Sprintf("ova-server-pvc-%s-%s", r.Source.Provider.Name, r.Plan.Name) - // If the provider runs in a different namespace then the destination VM, - // we need to create the PV and PVC to access the NFS. - if r.Plan.Spec.TargetNamespace != r.Source.Provider.Namespace { - r.CreatePvcForNfs(pvcName) + pvcName := fmt.Sprintf("ova-nfs-pvc-%s-%s", r.Source.Provider.Name, r.Plan.Name) + /* + TODO: set ownerReferences the PV and PVC deleted once the plan is done. + cross namespaces are not possible to be used for plan/provider ownership. + optional: PV+PVC per VM, deleted via cleanup. ownership: importer pod? DV? + ownerReference := meta.OwnerReference{ + APIVersion: "forklift.konveyor.io/v1beta1", + Kind: "Plan", + Name: r.Plan.Name, + UID: r.Plan.UID, + } + */ + err = r.CreatePvForNfs() + if err != nil { + return + } + err = r.CreatePvcForNfs(pvcName) + if err != nil { + return } //path from disk volumes = append(volumes, core.Volume{ - Name: "nfs-pvc", + Name: "nfs-pv", VolumeSource: core.VolumeSource{ - PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ ClaimName: pvcName, }, }, @@ -1363,7 +1379,7 @@ func (r *KubeVirt) podVolumeMounts(vmVolumes []cnv.Volume, configMap *core.Confi MountPath: "/opt", }, core.VolumeMount{ - Name: "nfs-pvc", + Name: "nfs-pv", MountPath: "/ova", }, ) @@ -1832,70 +1848,129 @@ func (r *KubeVirt) EnsurePersistentVolume(vmRef ref.Ref, persistentVolumes []cor return } -func (r *KubeVirt) CreatePvcForNfs(pvcName string) (err error) { - //TODO: set ownerReferenceso the PV and PVC deleted once the plan is done. - // ownerReference := meta.OwnerReference{ - // APIVersion: "forklift.konveyor.io/v1beta1", - // Kind: "Plan", - // Name: r.Plan.Name, - // UID: r.Plan.UID, - // } +func (r *KubeVirt) getOvaPvNfs() (found bool, err error) { + pv := &core.PersistentVolume{} + err = r.Destination.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: fmt.Sprintf("ova-nfs-pv-%s", r.Plan.Name), + }, + pv, + ) + + if err != nil { + if k8serr.IsNotFound(err) { + return false, nil + } + err = liberr.Wrap(err) + return + } + return true, nil +} +func (r *KubeVirt) getOvaPvcNfs() (found bool, err error) { + pvc := &core.PersistentVolumeClaim{} + err = r.Destination.Client.Get( + context.TODO(), + types.NamespacedName{ + Name: fmt.Sprintf("ova-nfs-pvc-%s-%s", r.Source.Provider.Name, r.Plan.Name), + Namespace: r.Plan.Spec.TargetNamespace, + }, + pvc, + ) + + if err != nil { + if k8serr.IsNotFound(err) { + return false, nil + } + err = liberr.Wrap(err) + return + } + return true, nil +} + +// TODO if we use ownership get it to the method +func (r *KubeVirt) CreatePvForNfs() (err error) { sourceProvider := r.Source.Provider - pvName := fmt.Sprintf("ova-server-pv-%s", r.Plan.Name) splitted := strings.Split(sourceProvider.Spec.URL, ":") - if len(splitted) != 2 { - r.Log.Error(nil, "NFS server path doesn't contains :") + r.Log.Error(nil, "NFS server path doesn't contains: ", "url", sourceProvider.Spec.URL) + return fmt.Errorf("bad source provider %s URL %s", sourceProvider.Name, sourceProvider.Spec.URL) } nfsServer := splitted[0] nfsPath := splitted[1] - pv := &v1.PersistentVolume{ + pvName := fmt.Sprintf("ova-nfs-pv-%s", r.Plan.Name) + found, err := r.getOvaPvNfs() + if err != nil { + return + } + if found { + r.Log.Info("The PV for OVA NFS exists", "PV", pvName) + return + } + + pv := &core.PersistentVolume{ ObjectMeta: meta.ObjectMeta{ Name: pvName, + // OwnerReferences: []meta.OwnerReference{ownerReference}, }, - Spec: v1.PersistentVolumeSpec{ - Capacity: v1.ResourceList{ - v1.ResourceStorage: resource.MustParse("1Gi"), + Spec: core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceStorage: resource.MustParse("1Gi"), }, - AccessModes: []v1.PersistentVolumeAccessMode{ - v1.ReadOnlyMany, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, }, - PersistentVolumeSource: v1.PersistentVolumeSource{ - NFS: &v1.NFSVolumeSource{ + PersistentVolumeSource: core.PersistentVolumeSource{ + NFS: &core.NFSVolumeSource{ Path: nfsPath, Server: nfsServer, }, }, }, } - err = r.Create(context.TODO(), pv) + err = r.Destination.Create(context.TODO(), pv) if err != nil { r.Log.Error(err, "Failed to create OVA plan PV") return } + return +} + +// TODO if we use ownership get it to the method +func (r *KubeVirt) CreatePvcForNfs(pvcName string) (err error) { + found, err := r.getOvaPvcNfs() + if err != nil { + return + } + if found { + r.Log.Info("The PVC for OVA NFS exists", "PVC", pvcName) + return + } sc := "" - pvc := &v1.PersistentVolumeClaim{ + pvName := fmt.Sprintf("ova-nfs-pv-%s", r.Plan.Name) + pvc := &core.PersistentVolumeClaim{ ObjectMeta: meta.ObjectMeta{ Name: pvcName, Namespace: r.Plan.Spec.TargetNamespace, + // OwnerReferences: []meta.OwnerReference{ownerReference}, }, - Spec: v1.PersistentVolumeClaimSpec{ - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceStorage: resource.MustParse("1Gi"), + Spec: core.PersistentVolumeClaimSpec{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceStorage: resource.MustParse("1Gi"), }, }, - AccessModes: []v1.PersistentVolumeAccessMode{ - v1.ReadOnlyMany, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, }, VolumeName: pvName, StorageClassName: &sc, }, } - err = r.Create(context.TODO(), pvc) + err = r.Destination.Create(context.TODO(), pvc) if err != nil { r.Log.Error(err, "Failed to create OVA plan PVC") return @@ -1913,15 +1988,13 @@ func (r *KubeVirt) CreatePvcForNfs(pvcName string) (err error) { select { case <-timeout: r.Log.Error(err, "Timed out waiting for PVC to be bound") - return + return fmt.Errorf("timeout passed waiting for the OVA PVC %v", pvc) case <-tick: err = r.Get(context.TODO(), pvcNamespacedName, pvc) - fmt.Print("this is pvc: ", pvc) if err != nil { r.Log.Error(err, "Failed to bound OVA plan PVC") return } - if pvc.Status.Phase == "Bound" { return } diff --git a/pkg/controller/provider/ova-setup.go b/pkg/controller/provider/ova-setup.go index 294b87c5f..6933cd823 100644 --- a/pkg/controller/provider/ova-setup.go +++ b/pkg/controller/provider/ova-setup.go @@ -8,7 +8,7 @@ import ( api "github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1" appsv1 "k8s.io/api/apps/v1" - v1 "k8s.io/api/core/v1" + core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -22,66 +22,93 @@ const ( pvSize = "1Gi" ) -func (r *Reconciler) CreateOVAServerDeployment(provider *api.Provider, ctx context.Context) { - +func (r Reconciler) CreateOVAServerDeployment(provider *api.Provider, ctx context.Context) { ownerReference := metav1.OwnerReference{ APIVersion: "forklift.konveyor.io/v1beta1", Kind: "Provider", Name: provider.Name, UID: provider.UID, } - pvName := fmt.Sprintf("%s-pv-%s", ovaServerPrefix, provider.Name) - splitted := strings.Split(provider.Spec.URL, ":") + err := r.createPvForNfs(provider, ctx, ownerReference, pvName) + if err != nil { + r.Log.Error(err, "Failed to create NFS PV for the OVA server") + return + } + + pvcName := fmt.Sprintf("%s-pvc-%s", ovaServerPrefix, provider.Name) + err = r.createPvcForNfs(provider, ctx, ownerReference, pvName, pvcName) + if err != nil { + r.Log.Error(err, "Failed to create NFS PVC for the OVA server") + return + } + + labels := map[string]string{"providerName": provider.Name, "app": "forklift"} + err = r.createServerDeployment(provider, ctx, ownerReference, pvcName, labels) + if err != nil { + r.Log.Error(err, "Failed to create OVA server deployment") + return + } + + err = r.createServerService(provider, ctx, ownerReference, labels) + if err != nil { + r.Log.Error(err, "Failed to create OVA server service") + return + } +} +func (r *Reconciler) createPvForNfs(provider *api.Provider, ctx context.Context, ownerReference metav1.OwnerReference, pvName string) (err error) { + splitted := strings.Split(provider.Spec.URL, ":") if len(splitted) != 2 { - r.Log.Error(nil, "NFS server path doesn't contains :") + r.Log.Error(nil, "NFS server path doesn't contains: ", "provider", provider, "url", provider.Spec.URL) + return fmt.Errorf("wrong NFS server path") } nfsServer := splitted[0] nfsPath := splitted[1] - pv := &v1.PersistentVolume{ + pv := &core.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: pvName, OwnerReferences: []metav1.OwnerReference{ownerReference}, }, - Spec: v1.PersistentVolumeSpec{ - Capacity: v1.ResourceList{ - v1.ResourceStorage: resource.MustParse("1Gi"), + Spec: core.PersistentVolumeSpec{ + Capacity: core.ResourceList{ + core.ResourceStorage: resource.MustParse(pvSize), }, - AccessModes: []v1.PersistentVolumeAccessMode{ - v1.ReadOnlyMany, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, }, - PersistentVolumeSource: v1.PersistentVolumeSource{ - NFS: &v1.NFSVolumeSource{ + PersistentVolumeSource: core.PersistentVolumeSource{ + NFS: &core.NFSVolumeSource{ Path: nfsPath, Server: nfsServer, }, }, }, } - err := r.Create(ctx, pv) + err = r.Create(ctx, pv) if err != nil { - r.Log.Error(err, "Failed to create OVA server PV") return } + return +} - pvcName := fmt.Sprintf("%s-pvc-%s", ovaServerPrefix, provider.Name) +func (r *Reconciler) createPvcForNfs(provider *api.Provider, ctx context.Context, ownerReference metav1.OwnerReference, pvName, pvcName string) (err error) { sc := "" - pvc := &v1.PersistentVolumeClaim{ + pvc := &core.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: pvcName, Namespace: provider.Namespace, OwnerReferences: []metav1.OwnerReference{ownerReference}, }, - Spec: v1.PersistentVolumeClaimSpec{ - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceStorage: resource.MustParse("1Gi"), + Spec: core.PersistentVolumeClaimSpec{ + Resources: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceStorage: resource.MustParse(pvSize), }, }, - AccessModes: []v1.PersistentVolumeAccessMode{ - v1.ReadOnlyMany, + AccessModes: []core.PersistentVolumeAccessMode{ + core.ReadOnlyMany, }, VolumeName: pvName, StorageClassName: &sc, @@ -89,16 +116,16 @@ func (r *Reconciler) CreateOVAServerDeployment(provider *api.Provider, ctx conte } err = r.Create(ctx, pvc) if err != nil { - r.Log.Error(err, "Failed to create OVA server PVC") return } + return +} +func (r *Reconciler) createServerDeployment(provider *api.Provider, ctx context.Context, ownerReference metav1.OwnerReference, pvcName string, labels map[string]string) (err error) { deploymentName := fmt.Sprintf("%s-deployment-%s", ovaServerPrefix, provider.Name) annotations := make(map[string]string) - labels := map[string]string{"providerName": provider.Name, "app": "forklift"} var replicas int32 = 1 - //OVA server deployment deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, @@ -114,88 +141,80 @@ func (r *Reconciler) CreateOVAServerDeployment(provider *api.Provider, ctx conte "app": "forklift", }, }, - Template: v1.PodTemplateSpec{ + Template: core.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "providerName": provider.Name, - "app": "forklift", - }, + Labels: labels, }, - Spec: r.makeOvaProviderPodSpec(pvcName, string(provider.Name)), + Spec: r.makeOvaProviderPodSpec(pvcName, provider.Name), }, }, } err = r.Create(ctx, deployment) if err != nil { - r.Log.Error(err, "Failed to create OVA server deployment") return } + return +} - // OVA Server Service +func (r *Reconciler) createServerService(provider *api.Provider, ctx context.Context, ownerReference metav1.OwnerReference, labels map[string]string) (err error) { serviceName := fmt.Sprintf("ova-service-%s", provider.Name) - service := &v1.Service{ + service := &core.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: provider.Namespace, Labels: labels, OwnerReferences: []metav1.OwnerReference{ownerReference}, }, - Spec: v1.ServiceSpec{ - Selector: map[string]string{ - "providerName": provider.Name, - "app": "forklift", - }, - Ports: []v1.ServicePort{ + Spec: core.ServiceSpec{ + Selector: labels, + Ports: []core.ServicePort{ { Name: "api-http", - Protocol: v1.ProtocolTCP, + Protocol: core.ProtocolTCP, Port: 8080, TargetPort: intstr.FromInt(8080), }, }, - Type: v1.ServiceTypeClusterIP, + Type: core.ServiceTypeClusterIP, }, } err = r.Create(ctx, service) if err != nil { - r.Log.Error(err, "Failed to create OVA server service") return } + return } -func (r *Reconciler) makeOvaProviderPodSpec(pvcName string, providerName string) v1.PodSpec { - +func (r *Reconciler) makeOvaProviderPodSpec(pvcName string, providerName string) core.PodSpec { imageName, ok := os.LookupEnv(ovaImageVar) if !ok { r.Log.Error(nil, "Failed to find OVA server image") } nfsVolumeName := fmt.Sprintf("%s-%s", nfsVolumeNamePrefix, providerName) - ovaContainerName := fmt.Sprintf("%s-pod-%s", ovaServerPrefix, providerName) - return v1.PodSpec{ - - Containers: []v1.Container{ + return core.PodSpec{ + Containers: []core.Container{ { Name: ovaContainerName, - Ports: []v1.ContainerPort{{ContainerPort: 8080, Protocol: v1.ProtocolTCP}}, + Ports: []core.ContainerPort{{ContainerPort: 8080, Protocol: core.ProtocolTCP}}, Image: imageName, - VolumeMounts: []v1.VolumeMount{ + VolumeMounts: []core.VolumeMount{ { Name: nfsVolumeName, - MountPath: "/ova", + MountPath: mountPath, }, }, }, }, - Volumes: []v1.Volume{ + Volumes: []core.Volume{ { Name: nfsVolumeName, - VolumeSource: v1.VolumeSource{ - PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + VolumeSource: core.VolumeSource{ + PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ ClaimName: pvcName, }, },