Skip to content

Commit

Permalink
Support specifying the transfer net by configmap
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
mansam committed Dec 2, 2024
1 parent fb5b40e commit cea94f9
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 13 deletions.
57 changes: 52 additions & 5 deletions pkg/controller/plan/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plan

import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand Down
81 changes: 73 additions & 8 deletions pkg/controller/plan/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"path"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit cea94f9

Please sign in to comment.