Skip to content

Commit

Permalink
udn: let OVN-K know the claim name when UDN is used
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Duarte Barroso <[email protected]>
  • Loading branch information
maiqueb committed Aug 26, 2024
1 parent 1e0b445 commit f78eb99
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 43 deletions.
1 change: 1 addition & 0 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
type RelevantConfig struct {
Name string `json:"name"`
AllowPersistentIPs bool `json:"allowPersistentIPs,omitempty"`
Role string `json:"role,omitempty"`
}

func NewConfig(nadSpec string) (*RelevantConfig, error) {
Expand Down
66 changes: 58 additions & 8 deletions pkg/ipamclaimswebhook/podmutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,28 @@ func (a *IPAMClaimsValet) Handle(ctx context.Context, request admission.Request)
return admission.Errored(http.StatusBadRequest, err)
}

log.Info("webhook handling event")
vmName, hasVMAnnotation := pod.Annotations["kubevirt.io/domain"]
log.Info("webhook handling event - checking primary UDN flow for", "VM", vmName, "namespace", pod.Namespace)
primaryUDNNetwork, err := a.vmiPrimaryUDN(ctx, pod.Namespace)
if err != nil {
// TODO: figure out what to do. Probably fail
return admission.Errored(http.StatusInternalServerError, err)
}

if primaryUDNNetwork != nil {
log.Info("found primary UDN for", "vmName", vmName, "namespace", pod.Namespace, "primary UDN name", primaryUDNNetwork.Name)

Check failure on line 77 in pkg/ipamclaimswebhook/podmutator.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 125 characters (lll)
annotatePodWithUDN(pod, vmName, primaryUDNNetwork.Name)
}

log.Info("webhook handling event - checking secondary networks flow for", "pod", pod.Name, "namespace", pod.Namespace)
networkSelectionElements, err := netutils.ParsePodNetworkAnnotation(pod)
if err != nil {
var goodTypeOfError *v1.NoK8sNetworkError
if errors.As(err, &goodTypeOfError) {
if errors.As(err, &goodTypeOfError) && primaryUDNNetwork == nil {
return admission.Allowed("no secondary networks requested")
} else if primaryUDNNetwork == nil {
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 (
Expand Down Expand Up @@ -112,7 +126,6 @@ func (a *IPAMClaimsValet) Handle(ctx context.Context, request admission.Request)
"NAD", nadName,
"network", pluginConfig.Name,
)
vmName, hasVMAnnotation := pod.Annotations["kubevirt.io/domain"]
if !hasVMAnnotation {
log.Info(
"does not have the kubevirt VM annotation",
Expand Down Expand Up @@ -154,13 +167,14 @@ func (a *IPAMClaimsValet) Handle(ctx context.Context, request admission.Request)
podNetworkSelectionElements = append(podNetworkSelectionElements, *networkSelectionElement)
}

if len(podNetworkSelectionElements) > 0 {
if len(podNetworkSelectionElements) > 0 || primaryUDNNetwork != nil {
log.Info("WILL PATCH THE POD")

newPod, err := podWithUpdatedSelectionElements(pod, podNetworkSelectionElements)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

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

Expand Down Expand Up @@ -202,6 +216,42 @@ func podWithUpdatedSelectionElements(pod *corev1.Pod, networks []v1.NetworkSelec
if err != nil {
return nil, err
}
newPod.Annotations[v1.NetworkAttachmentAnnot] = string(newNets)
if string(newNets) != "[]" {
newPod.Annotations[v1.NetworkAttachmentAnnot] = string(newNets)
}
return newPod, nil
}

func annotatePodWithUDN(pod *corev1.Pod, vmName string, primaryUDNName string) {
const ovnUDNIPAMClaimName = "k8s.ovn.org/ovn-udn-ipamclaim-reference"
udnAnnotations := map[string]string{
ovnUDNIPAMClaimName: fmt.Sprintf("%s.%s-primary-udn", vmName, primaryUDNName),
}
pod.SetAnnotations(udnAnnotations)
}

func (a *IPAMClaimsValet) vmiPrimaryUDN(ctx context.Context, namespace string) (*config.RelevantConfig, error) {
const (
NetworkRolePrimary = "primary"
NetworkRoleSecondary = "secondary"
)

log := logf.FromContext(ctx)
var namespaceNads v1.NetworkAttachmentDefinitionList
if err := a.List(ctx, &namespaceNads, &client.ListOptions{}); err != nil {
return nil, fmt.Errorf("failed to list the NADs on namespace %q: %v", namespace, err)
}

for _, nad := range namespaceNads.Items {
networkConfig, err := config.NewConfig(nad.Spec.Config)
if err != nil {
log.Error(err, "failed extracting the relevant NAD configuration", "NAD name", nad.Name, "NAD namespace", nad.Namespace)

Check failure on line 248 in pkg/ipamclaimswebhook/podmutator.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 123 characters (lll)
return nil, fmt.Errorf("failed to extract the relevant NAD information")
}

if networkConfig.Role == NetworkRolePrimary {
return networkConfig, nil
}
}
return nil, nil
}
125 changes: 90 additions & 35 deletions pkg/vminetworkscontroller/vmi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,44 +83,27 @@ func (r *VirtualMachineInstanceReconciler) Reconcile(
}

ownerInfo := ownerReferenceFor(vmi, vm)
for logicalNetworkName, netConfigName := range vmiNetworks {
claimKey := fmt.Sprintf("%s.%s", vmi.Name, logicalNetworkName)
ipamClaim := &ipamclaimsapi.IPAMClaim{
ObjectMeta: controllerruntime.ObjectMeta{
Name: claimKey,
Namespace: vmi.Namespace,
OwnerReferences: []metav1.OwnerReference{ownerInfo},
Finalizers: []string{claims.KubevirtVMFinalizer},
Labels: claims.OwnedByVMLabel(vmi.Name),
},
Spec: ipamclaimsapi.IPAMClaimSpec{
Network: netConfigName,
},
}

if err := r.Client.Create(ctx, ipamClaim, &client.CreateOptions{}); err != nil {
if apierrors.IsAlreadyExists(err) {
claimKey := apitypes.NamespacedName{
Namespace: vmi.Namespace,
Name: claimKey,
}
// UDN code block
primaryUDN, err := r.vmiPrimaryUDN(ctx, vmi)
if err != nil {
return controllerruntime.Result{}, err
}
if primaryUDN != nil {
claimKey := fmt.Sprintf("%s.%s-primary-udn", vmi.Name, primaryUDN.Name)
udnIPAMClaim := newIPAMClaim(claimKey, vmi, ownerInfo, primaryUDN.Name)
if err := r.ensureIPAMClaim(ctx, udnIPAMClaim, vmi, ownerInfo); err != nil {
return controllerruntime.Result{}, fmt.Errorf("failed ensuring IPAM claim for primary UDN network %q: %w", primaryUDN.Name, err)

Check failure on line 96 in pkg/vminetworkscontroller/vmi_controller.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 131 characters (lll)
}
}
// UDN code block END

existingIPAMClaim := &ipamclaimsapi.IPAMClaim{}
if err := r.Client.Get(ctx, claimKey, existingIPAMClaim); err != nil {
return controllerruntime.Result{}, fmt.Errorf("let us be on the safe side and retry later")
}
for logicalNetworkName, netConfigName := range vmiNetworks {
claimKey := fmt.Sprintf("%s.%s", vmi.Name, logicalNetworkName)
ipamClaim := newIPAMClaim(claimKey, vmi, ownerInfo, netConfigName)

if len(existingIPAMClaim.OwnerReferences) == 1 && existingIPAMClaim.OwnerReferences[0].UID == ownerInfo.UID {
r.Log.Info("found existing IPAMClaim belonging to this VM/VMI, nothing to do", "UID", ownerInfo.UID)
continue
} else {
err := fmt.Errorf("failed since it found an existing IPAMClaim for %q", claimKey.Name)
r.Log.Error(err, "leaked IPAMClaim found", "existing owner", existingIPAMClaim.UID)
return controllerruntime.Result{}, err
}
}
r.Log.Error(err, "failed to create the IPAMClaim")
return controllerruntime.Result{}, err
if err := r.ensureIPAMClaim(ctx, ipamClaim, vmi, ownerInfo); err != nil {
return controllerruntime.Result{}, fmt.Errorf("failed ensuring IPAM claim: %w", err)
}
}

Expand Down Expand Up @@ -242,3 +225,75 @@ func getOwningVM(ctx context.Context, c client.Client, name apitypes.NamespacedN
return nil, fmt.Errorf("failed getting VM %q: %w", name, err)
}
}

func (r *VirtualMachineInstanceReconciler) vmiPrimaryUDN(
ctx context.Context,
vmi *virtv1.VirtualMachineInstance,
) (*config.RelevantConfig, error) {
const (
NetworkRolePrimary = "primary"
NetworkRoleSecondary = "secondary"
)

var namespaceNads nadv1.NetworkAttachmentDefinitionList
if err := r.List(ctx, &namespaceNads, &client.ListOptions{}); err != nil {
return nil, fmt.Errorf("failed to list the NADs on namespace %q: %v", vmi.Namespace, err)
}

for _, nad := range namespaceNads.Items {
networkConfig, err := config.NewConfig(nad.Spec.Config)
if err != nil {
r.Log.Error(err, "failed extracting the relevant NAD configuration", "NAD name", nad.Name, "NAD namespace", nad.Namespace)

Check failure on line 246 in pkg/vminetworkscontroller/vmi_controller.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 125 characters (lll)
return nil, fmt.Errorf("failed to extract the relevant NAD information")
}

if networkConfig.Role == NetworkRolePrimary {
return networkConfig, nil
}
}
return nil, nil
}

func newIPAMClaim(claimKey string, vmi *virtv1.VirtualMachineInstance, ownerInfo metav1.OwnerReference, netConfigName string) *ipamclaimsapi.IPAMClaim {

Check failure on line 257 in pkg/vminetworkscontroller/vmi_controller.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 152 characters (lll)
return &ipamclaimsapi.IPAMClaim{
ObjectMeta: controllerruntime.ObjectMeta{
Name: claimKey,
Namespace: vmi.Namespace,
OwnerReferences: []metav1.OwnerReference{ownerInfo},
Finalizers: []string{claims.KubevirtVMFinalizer},
Labels: claims.OwnedByVMLabel(vmi.Name),
},
Spec: ipamclaimsapi.IPAMClaimSpec{
Network: netConfigName,
},
}
}

func (r *VirtualMachineInstanceReconciler) ensureIPAMClaim(ctx context.Context, ipamClaim *ipamclaimsapi.IPAMClaim, vmi *virtv1.VirtualMachineInstance, ownerInfo metav1.OwnerReference) error {

Check failure on line 272 in pkg/vminetworkscontroller/vmi_controller.go

View workflow job for this annotation

GitHub Actions / build-linters-unit-tests

line is 192 characters (lll)
claimKey := ipamClaim.Name
if err := r.Client.Create(ctx, ipamClaim, &client.CreateOptions{}); err != nil {
if apierrors.IsAlreadyExists(err) {
claimKey := apitypes.NamespacedName{
Namespace: vmi.Namespace,
Name: claimKey,
}

existingIPAMClaim := &ipamclaimsapi.IPAMClaim{}
if err := r.Client.Get(ctx, claimKey, existingIPAMClaim); err != nil {
return fmt.Errorf("let us be on the safe side and retry later")
}

if len(existingIPAMClaim.OwnerReferences) == 1 && existingIPAMClaim.OwnerReferences[0].UID == ownerInfo.UID {
r.Log.Info("found existing IPAMClaim belonging to this VM/VMI, nothing to do", "UID", ownerInfo.UID)
return nil
} else {
err := fmt.Errorf("failed since it found an existing IPAMClaim for %q", claimKey.Name)
r.Log.Error(err, "leaked IPAMClaim found", "existing owner", existingIPAMClaim.UID)
return err
}
}
r.Log.Error(err, "failed to create the IPAMClaim")
return err
}
return nil
}

0 comments on commit f78eb99

Please sign in to comment.