Skip to content

Commit

Permalink
Merge pull request #36 from qinqon/support-primary-network
Browse files Browse the repository at this point in the history
Support primary network
  • Loading branch information
kubevirt-bot committed Sep 5, 2024
2 parents 64fc826 + bdabec2 commit 7348bbf
Show file tree
Hide file tree
Showing 7 changed files with 426 additions and 172 deletions.
4 changes: 4 additions & 0 deletions pkg/claims/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ func OwnedByVMLabel(vmiName string) client.MatchingLabels {
virtv1.VirtualMachineLabel: vmiName,
}
}

func ComposeKey(vmName, networkName string) string {
return fmt.Sprintf("%s.%s", vmName, networkName)
}
13 changes: 11 additions & 2 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ import (
"fmt"
)

type NetworkRole string

const (
NetworkRolePrimary NetworkRole = "primary"
)

const OVNPrimaryNetworkIPAMClaimAnnotation = "k8s.ovn.org/ovn-udn-ipamclaim-reference"

type RelevantConfig struct {
Name string `json:"name"`
AllowPersistentIPs bool `json:"allowPersistentIPs,omitempty"`
Name string `json:"name"`
AllowPersistentIPs bool `json:"allowPersistentIPs,omitempty"`
Role NetworkRole `json:"role,omitempty"`
}

func NewConfig(nadSpec string) (*RelevantConfig, error) {
Expand Down
253 changes: 166 additions & 87 deletions pkg/ipamclaimswebhook/podmutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import (

virtv1 "kubevirt.io/api/core/v1"

"github.com/kubevirt/ipam-extensions/pkg/claims"
"github.com/kubevirt/ipam-extensions/pkg/config"
"github.com/kubevirt/ipam-extensions/pkg/udn"
)

// +kubebuilder:webhook:path=/mutate-v1-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=ipam-claims.k8s.cni.cncf.io,admissionReviewVersions=v1,sideEffects=None
Expand All @@ -66,101 +68,52 @@ func (a *IPAMClaimsValet) Handle(ctx context.Context, request admission.Request)
}

log.Info("webhook handling event")

vmName, hasVMAnnotation := pod.Annotations["kubevirt.io/domain"]
if !hasVMAnnotation {
log.Info(
"does not have the kubevirt VM annotation",
)
return admission.Allowed("not a VM")
}

networkSelectionElements, err := netutils.ParsePodNetworkAnnotation(pod)
if err != nil {
var goodTypeOfError *v1.NoK8sNetworkError
if errors.As(err, &goodTypeOfError) {
return admission.Allowed("no secondary networks requested")
if !errors.As(err, &goodTypeOfError) {
return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to parse pod network selection elements"))
}
return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to parse pod network selection elements"))
}

var (
hasChangedNetworkSelectionElements bool
podNetworkSelectionElements = make([]v1.NetworkSelectionElement, 0, len(networkSelectionElements))
)
for _, networkSelectionElement := range networkSelectionElements {
nadName := types.NamespacedName{
Namespace: networkSelectionElement.Namespace,
Name: networkSelectionElement.Name,
}.String()
log.Info(
"iterating network selection elements",
"NAD", nadName,
)
nadKey := types.NamespacedName{
Namespace: networkSelectionElement.Namespace,
Name: networkSelectionElement.Name,
}

nad := v1.NetworkAttachmentDefinition{}
if err := a.Client.Get(context.Background(), nadKey, &nad); err != nil {
if k8serrors.IsNotFound(err) {
return admission.Allowed("NAD not found, will hang on scheduler")
}
return admission.Errored(http.StatusInternalServerError, err)
var newPod *corev1.Pod
hasChangedNetworkSelectionElements, err :=
ensureIPAMClaimRefAtNetworkSelectionElements(ctx, a.Client, pod, vmName, networkSelectionElements)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
if hasChangedNetworkSelectionElements {
if newPod == nil {
newPod = pod.DeepCopy()
}

pluginConfig, err := config.NewConfig(nad.Spec.Config)
if err != nil {
if err := updatePodSelectionElements(newPod, networkSelectionElements); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

if pluginConfig.AllowPersistentIPs {
log.Info(
"will request persistent IPs",
"NAD", nadName,
"network", pluginConfig.Name,
)
vmName, hasVMAnnotation := pod.Annotations["kubevirt.io/domain"]
if !hasVMAnnotation {
log.Info(
"does not have the kubevirt VM annotation",
"NAD", nadName,
"network", pluginConfig.Name,
)
return admission.Allowed("not a VM")
}

vmKey := types.NamespacedName{Namespace: pod.Namespace, Name: vmName}
vmi := &virtv1.VirtualMachineInstance{}
if err := a.Client.Get(context.Background(), vmKey, vmi); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

vmiNets := vmiSecondaryNetworks(vmi)
networkName, foundNetworkName := vmiNets[nadKey.String()]
if !foundNetworkName {
log.Info(
"network name not found",
"NAD", nadName,
"network", networkName,
)
podNetworkSelectionElements = append(podNetworkSelectionElements, *networkSelectionElement)
continue
}

networkSelectionElement.IPAMClaimReference = fmt.Sprintf("%s.%s", vmName, networkName)
log.Info(
"requesting claim",
"NAD", nadName,
"network", pluginConfig.Name,
"claim", networkSelectionElement.IPAMClaimReference,
)
podNetworkSelectionElements = append(podNetworkSelectionElements, *networkSelectionElement)
hasChangedNetworkSelectionElements = true
continue
}
podNetworkSelectionElements = append(podNetworkSelectionElements, *networkSelectionElement)
}

if len(podNetworkSelectionElements) > 0 {
newPod, err := podWithUpdatedSelectionElements(pod, podNetworkSelectionElements)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
newPrimaryNetworkIPAMClaimName, err := findNewPrimaryNetworkIPAMClaimName(ctx, a.Client, pod, vmName)
if err != nil {
return admission.Errored(http.StatusInternalServerError,
fmt.Errorf("failed looking for primary user defined IPAMClaim name: %v", err))
}
if newPrimaryNetworkIPAMClaimName != "" {
if newPod == nil {
newPod = pod.DeepCopy()
}
updatePodWithOVNPrimaryNetworkIPAMClaimAnnotation(newPod, newPrimaryNetworkIPAMClaimName)
}

if reflect.DeepEqual(newPod, pod) || !hasChangedNetworkSelectionElements {
if newPod != nil {
if reflect.DeepEqual(newPod, pod) {
return admission.Allowed("mutation not needed")
}

Expand All @@ -172,6 +125,7 @@ func (a *IPAMClaimsValet) Handle(ctx context.Context, request admission.Request)
log.Info("new pod annotations", "pod", newPod.Annotations)
return admission.PatchResponseFromRaw(request.Object.Raw, marshaledPod)
}

return admission.Allowed("carry on")
}

Expand All @@ -196,12 +150,137 @@ func vmiSecondaryNetworks(vmi *virtv1.VirtualMachineInstance) map[string]string
return indexedSecondaryNetworks
}

func podWithUpdatedSelectionElements(pod *corev1.Pod, networks []v1.NetworkSelectionElement) (*corev1.Pod, error) {
newPod := pod.DeepCopy()
func updatePodSelectionElements(pod *corev1.Pod, networks []*v1.NetworkSelectionElement) error {
newNets, err := json.Marshal(networks)
if err != nil {
return nil, err
return err
}
pod.Annotations[v1.NetworkAttachmentAnnot] = string(newNets)
return nil
}

func updatePodWithOVNPrimaryNetworkIPAMClaimAnnotation(pod *corev1.Pod, primaryNetworkIPAMClaimName string) {
pod.Annotations[config.OVNPrimaryNetworkIPAMClaimAnnotation] = primaryNetworkIPAMClaimName
}

func ensureIPAMClaimRefAtNetworkSelectionElements(ctx context.Context,
cli client.Client, pod *corev1.Pod, vmName string,
networkSelectionElements []*v1.NetworkSelectionElement) (changed bool, err error) {
log := logf.FromContext(ctx)
hasChangedNetworkSelectionElements := false
for i, networkSelectionElement := range networkSelectionElements {
nadName := fmt.Sprintf("%s/%s", networkSelectionElement.Namespace, networkSelectionElement.Name)
log.Info(
"iterating network selection elements",
"NAD", nadName,
)
nadKey := types.NamespacedName{
Namespace: networkSelectionElement.Namespace,
Name: networkSelectionElement.Name,
}

nad := v1.NetworkAttachmentDefinition{}
if err := cli.Get(context.Background(), nadKey, &nad); err != nil {
if k8serrors.IsNotFound(err) {
log.Info("NAD not found, will hang on scheduler", "NAD", nadName)
return false, nil
}
return false, err
}

pluginConfig, err := config.NewConfig(nad.Spec.Config)
if err != nil {
return false, err
}

if !pluginConfig.AllowPersistentIPs {
continue
}

log.Info(
"will request persistent IPs",
"NAD", nadName,
"network", pluginConfig.Name,
)

vmKey := types.NamespacedName{Namespace: pod.Namespace, Name: vmName}
vmi := &virtv1.VirtualMachineInstance{}
if err := cli.Get(context.Background(), vmKey, vmi); err != nil {
return false, err
}

vmiNets := vmiSecondaryNetworks(vmi)
networkName, foundNetworkName := vmiNets[nadKey.String()]
if !foundNetworkName {
log.Info(
"network name not found",
"NAD", nadName,
"network", networkName,
)
continue
}

networkSelectionElements[i].IPAMClaimReference = claims.ComposeKey(vmName, networkName)
log.Info(
"requesting claim",
"NAD", nadName,
"network", pluginConfig.Name,
"claim", networkSelectionElement.IPAMClaimReference,
)
hasChangedNetworkSelectionElements = true
continue
}
return hasChangedNetworkSelectionElements, nil
}

func findNewPrimaryNetworkIPAMClaimName(ctx context.Context,
cli client.Client, pod *corev1.Pod, vmName string) (string, error) {
log := logf.FromContext(ctx)
if pod.Annotations[config.OVNPrimaryNetworkIPAMClaimAnnotation] != "" {
return "", nil
}
primaryNetworkNAD, err := udn.FindPrimaryNetwork(ctx, cli, pod.Namespace)
if err != nil {
return "", err
}
if primaryNetworkNAD == nil {
return "", nil
}
pluginConfig, err := config.NewConfig(primaryNetworkNAD.Spec.Config)
if err != nil {
return "", err
}

if !pluginConfig.AllowPersistentIPs {
return "", nil
}

log.Info(
"will request primary network persistent IPs",
"NAD", client.ObjectKeyFromObject(primaryNetworkNAD),
"network", pluginConfig.Name,
)
vmKey := types.NamespacedName{Namespace: pod.Namespace, Name: vmName}
vmi := &virtv1.VirtualMachineInstance{}
if err := cli.Get(context.Background(), vmKey, vmi); err != nil {
return "", err
}

networkName := vmiPodNetworkName(vmi)
if networkName == "" {
log.Info("vmi has no pod network primary UDN ipam claim will not be created", "vmi", vmKey.String())
return "", nil
}

return claims.ComposeKey(vmi.Name, networkName), nil
}

// returns the name of the kubevirt VM pod network
func vmiPodNetworkName(vmi *virtv1.VirtualMachineInstance) string {
for _, network := range vmi.Spec.Networks {
if network.Pod != nil {
return network.Name
}
}
newPod.Annotations[v1.NetworkAttachmentAnnot] = string(newNets)
return newPod, nil
return ""
}
Loading

0 comments on commit 7348bbf

Please sign in to comment.