From cea94f906800970f7be061d1640c69c6c98dcfd9 Mon Sep 17 00:00:00 2001 From: Sam Lucidi Date: Mon, 2 Dec 2024 15:06:11 -0500 Subject: [PATCH] Support specifying the transfer net by configmap The k8s.v1.cni.cncf.org/networks annotation supports a json value with a list of NetworkSelectionElements that contain additional configuration for the secondary network attachment. This change allows the user to specify the transfer network by referencing a ConfigMap that contains a NetworkSelectionElement. Signed-off-by: Sam Lucidi --- pkg/controller/plan/kubevirt.go | 57 ++++++++++++++++++++-- pkg/controller/plan/validation.go | 81 ++++++++++++++++++++++++++++--- 2 files changed, 125 insertions(+), 13 deletions(-) diff --git a/pkg/controller/plan/kubevirt.go b/pkg/controller/plan/kubevirt.go index 0d5be1f30..4609e807b 100644 --- a/pkg/controller/plan/kubevirt.go +++ b/pkg/controller/plan/kubevirt.go @@ -2,6 +2,7 @@ package plan import ( "context" + "encoding/json" "encoding/xml" "errors" "fmt" @@ -53,7 +54,9 @@ import ( // Annotations const ( - // Transfer network annotation (value=network-attachment-definition name) + // Legacy transfer network annotation + AnnMultusDefaultNetwork = "v1.multus-cni.io/default-network" + // Transfer network annotation (value=[]NetworkSelectionElement) AnnTransferNetwork = "k8s.v1.cni.cncf.io/networks" // Contains validations for a Kubevirt VM. Needs to be removed when // creating a VM from a template. @@ -1227,8 +1230,10 @@ func (r *KubeVirt) dataVolumes(vm *plan.VMStatus, secret *core.Secret, configMap annotations[planbase.AnnRetainAfterCompletion] = "true" } if r.Plan.Spec.TransferNetwork != nil { - annotations[AnnTransferNetwork] = path.Join( - r.Plan.Spec.TransferNetwork.Namespace, r.Plan.Spec.TransferNetwork.Name) + err = r.setTransferNetworkAnnotation(annotations) + if err != nil { + return + } } if r.Plan.Spec.Warm || !r.Destination.Provider.IsHost() || r.Plan.IsSourceProviderOCP() { // Set annotation for WFFC storage classes. Note that we create data volumes while @@ -1757,8 +1762,10 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, // pod annotations annotations := map[string]string{} if r.Plan.Spec.TransferNetwork != nil { - annotations[AnnTransferNetwork] = path.Join( - r.Plan.Spec.TransferNetwork.Namespace, r.Plan.Spec.TransferNetwork.Name) + err = r.setTransferNetworkAnnotation(annotations) + if err != nil { + return + } } // pod pod = &core.Pod{ @@ -2351,6 +2358,46 @@ func (r *KubeVirt) vmAllButMigrationLabels(vmRef ref.Ref) (labels map[string]str return } +// Set the transfer network annotation (either the namespaced name of the NAD, or a NetworkSelectionElement) +func (r *KubeVirt) setTransferNetworkAnnotation(annotations map[string]string) (err error) { + if r.Plan.Spec.TransferNetwork != nil { + switch r.Plan.Spec.TransferNetwork.Kind { + case ConfigMap: + var configMap *core.ConfigMap + configMap, err = r.getTransferNetworkConfigMap() + var bytes []byte + bytes, err = json.Marshal([]map[string]string{configMap.Data}) + if err != nil { + err = liberr.Wrap(err) + return + } + annotations[AnnTransferNetwork] = fmt.Sprintf("%s", bytes) + if err != nil { + return + } + default: + annotations[AnnMultusDefaultNetwork] = path.Join( + r.Plan.Spec.TransferNetwork.Namespace, r.Plan.Spec.TransferNetwork.Name) + } + } + return +} + +// Get the configMap containing the NetworkSelectionElement for the transfer network. +func (r *KubeVirt) getTransferNetworkConfigMap() (configMap *core.ConfigMap, err error) { + configMap = &core.ConfigMap{} + err = r.Client.Get( + context.TODO(), + client.ObjectKey{Name: r.Plan.Spec.TransferNetwork.Name, Namespace: r.Plan.Spec.TransferNetwork.Namespace}, + configMap, + ) + if err != nil { + err = liberr.Wrap(err) + return + } + return +} + // Represents a CDI DataVolume, its associated PVC, and added behavior. type ExtendedDataVolume struct { *cdi.DataVolume diff --git a/pkg/controller/plan/validation.go b/pkg/controller/plan/validation.go index 9800304c2..29545bdb2 100644 --- a/pkg/controller/plan/validation.go +++ b/pkg/controller/plan/validation.go @@ -4,6 +4,7 @@ import ( "context" "crypto/md5" "encoding/hex" + "encoding/json" "errors" "fmt" "path" @@ -104,6 +105,11 @@ const ( False = libcnd.False ) +// Kinds +const ( + ConfigMap = "ConfigMap" +) + // Validate the plan resource. func (r *Reconciler) validate(plan *api.Plan) error { // Provider. @@ -628,29 +634,88 @@ func (r *Reconciler) validateVM(plan *api.Plan) error { return nil } -// Validate transfer network selection. -func (r *Reconciler) validateTransferNetwork(plan *api.Plan) (err error) { - if plan.Spec.TransferNetwork == nil { +// validateTransferNetworkConfigMap checks for the presence of a configMap matching the specified +// namespace and name, checks that the content is a valid NetworkSelectionElement, and then ensures +// that the NetworkAttachmentDefinition specified by the NSE exists. +func (r *Reconciler) validateTransferNetworkConfigMap(plan *api.Plan, key client.ObjectKey) (err error) { + notFound := libcnd.Condition{ + Type: TransferNetNotValid, + Status: True, + Category: Critical, + Reason: NotFound, + Message: "Transfer network ConfigMap could not be found.", + } + notValid := libcnd.Condition{ + Type: TransferNetNotValid, + Status: True, + Category: Critical, + Reason: NotValid, + Message: "Transfer network ConfigMap not valid.", + } + configMap := &v1.ConfigMap{} + err = r.Get(context.TODO(), key, configMap) + if err != nil { + if k8serr.IsNotFound(err) { + err = nil + plan.Status.SetCondition(notFound) + return + } + return + } + var bytes []byte + bytes, err = json.Marshal(configMap.Data) + if err != nil { + err = nil + plan.Status.SetCondition(notValid) return } + var nse net.NetworkSelectionElement + err = json.Unmarshal(bytes, &nse) + if err != nil { + err = nil + plan.Status.SetCondition(notValid) + return + } + key.Namespace = nse.Namespace + key.Name = nse.Name + err = r.validateTransferNetworkAttachmentDefinition(plan, key) + return +} + +func (r *Reconciler) validateTransferNetworkAttachmentDefinition(plan *api.Plan, key client.ObjectKey) (err error) { notFound := libcnd.Condition{ Type: TransferNetNotValid, Status: True, Category: Critical, Reason: NotFound, - Message: "Transfer network is not valid.", - } - key := client.ObjectKey{ - Namespace: plan.Spec.TransferNetwork.Namespace, - Name: plan.Spec.TransferNetwork.Name, + Message: "Transfer network could not be found.", } netAttachDef := &net.NetworkAttachmentDefinition{} err = r.Get(context.TODO(), key, netAttachDef) if k8serr.IsNotFound(err) { err = nil plan.Status.SetCondition(notFound) + } + return +} + +// Validate transfer network selection. +func (r *Reconciler) validateTransferNetwork(plan *api.Plan) (err error) { + if plan.Spec.TransferNetwork == nil { return } + + key := client.ObjectKey{ + Namespace: plan.Spec.TransferNetwork.Namespace, + Name: plan.Spec.TransferNetwork.Name, + } + + switch plan.Spec.TransferNetwork.Kind { + case ConfigMap: + err = r.validateTransferNetworkConfigMap(plan, key) + default: + err = r.validateTransferNetworkAttachmentDefinition(plan, key) + } if err != nil { err = liberr.Wrap(err) }