@@ -586,6 +586,136 @@ func (service *HTTPRestService) CreateOrUpdateNetworkContainerInternal(req *cns.
586586 return returnCode
587587}
588588
589+ func (service * HTTPRestService ) ReconcileIPAssignment (podInfoByIP map [string ]cns.PodInfo , ncReqs []* cns.CreateNetworkContainerRequest ) types.ResponseCode {
590+ // index all the secondary IP configs for all the nc reqs, for easier lookup later on.
591+ allSecIPsIdx := make (map [string ]* cns.CreateNetworkContainerRequest )
592+ for i := range ncReqs {
593+ for _ , secIPConfig := range ncReqs [i ].SecondaryIPConfigs {
594+ allSecIPsIdx [secIPConfig .IPAddress ] = ncReqs [i ]
595+ }
596+ }
597+
598+ // we now need to reconcile IP assignment.
599+ // considering that a single pod may have multiple ips (such as in dual stack scenarios)
600+ // and that IP assignment in CNS (as done by requestIPConfigsHelper) does not allow
601+ // updates (it returns the existing state if one already exists for the pod's interface),
602+ // we need to assign all IPs for a pod interface or name+namespace at the same time.
603+ //
604+ // iterating over single IPs is not appropriate then, since assignment for the first IP for
605+ // a pod will prevent the second IP from being added. the following function call transforms
606+ // pod info indexed by ip address:
607+ //
608+ // {
609+ // "10.0.0.1": podInfo{interface: "aaa-eth0"},
610+ // "fe80::1": podInfo{interface: "aaa-eth0"},
611+ // }
612+ //
613+ // to pod IPs indexed by pod key (interface or name+namespace, depending on scenario):
614+ //
615+ // {
616+ // "aaa-eth0": podIPs{v4IP: 10.0.0.1, v6IP: fe80::1}
617+ // }
618+ //
619+ // such that we can iterate over pod interfaces, and assign all IPs for it at once.
620+
621+ podKeyToPodIPs , err := newPodKeyToPodIPsMap (podInfoByIP )
622+ if err != nil {
623+ logger .Errorf ("could not transform pods indexed by IP address to pod IPs indexed by interface: %v" , err )
624+ return types .UnexpectedError
625+ }
626+
627+ for podKey , podIPs := range podKeyToPodIPs {
628+ var (
629+ desiredIPs []string
630+ ncIDs []string
631+ )
632+
633+ var ips []net.IP
634+ if podIPs .v4IP != nil {
635+ ips = append (ips , podIPs .v4IP )
636+ }
637+
638+ if podIPs .v6IP != nil {
639+ ips = append (ips , podIPs .v6IP )
640+ }
641+
642+ for _ , ip := range ips {
643+ if ncReq , ok := allSecIPsIdx [ip .String ()]; ok {
644+ desiredIPs = append (desiredIPs , ip .String ())
645+ ncIDs = append (ncIDs , ncReq .NetworkContainerid )
646+ } else {
647+ // it might still be possible to see host networking pods here (where ips are not from ncs) if we are restoring using the kube podinfo provider
648+ // todo: once kube podinfo provider reconcile flow is removed, this line will not be necessary/should be removed.
649+ logger .Errorf ("ip %s assigned to pod %+v but not found in any nc" , ip , podIPs )
650+ }
651+ }
652+
653+ if len (desiredIPs ) == 0 {
654+ // this may happen for pods in the host network
655+ continue
656+ }
657+
658+ jsonContext , err := podIPs .OrchestratorContext ()
659+ if err != nil {
660+ logger .Errorf ("Failed to marshal KubernetesPodInfo, error: %v" , err )
661+ return types .UnexpectedError
662+ }
663+
664+ ipconfigsRequest := cns.IPConfigsRequest {
665+ DesiredIPAddresses : desiredIPs ,
666+ OrchestratorContext : jsonContext ,
667+ InfraContainerID : podIPs .InfraContainerID (),
668+ PodInterfaceID : podIPs .InterfaceID (),
669+ }
670+
671+ if _ , err := requestIPConfigsHelper (service , ipconfigsRequest ); err != nil {
672+ //nolint:staticcheck // SA1019: suppress deprecated logger.Printf usage. Todo: legacy logger usage is consistent in cns repo. Migrates when all logger usage is migrated
673+ logger .Errorf ("requestIPConfigsHelper failed for pod key %s, podInfo %+v, ncIDs %v, error: %v" , podKey , podIPs , ncIDs , err )
674+ return types .FailedToAllocateIPConfig
675+ }
676+ }
677+
678+ return types .Success
679+ }
680+
681+ func (service * HTTPRestService ) CreateNCs (ncReqs []* cns.CreateNetworkContainerRequest ) types.ResponseCode {
682+ for _ , ncReq := range ncReqs {
683+ returnCode := service .CreateOrUpdateNetworkContainerInternal (ncReq )
684+ if returnCode != types .Success {
685+ return returnCode
686+ }
687+ }
688+
689+ return types .Success
690+ }
691+
692+ func (service * HTTPRestService ) ReconcileIPAMStateForSwift (ncReqs []* cns.CreateNetworkContainerRequest , podInfoByIP map [string ]cns.PodInfo , nnc * v1alpha.NodeNetworkConfig ) types.ResponseCode {
693+ logger .Printf ("Reconciling CNS IPAM state with nc requests: [%+v], PodInfo [%+v], NNC: [%+v]" , ncReqs , podInfoByIP , nnc )
694+ // if no nc reqs, there is no CRD state yet
695+ if len (ncReqs ) == 0 {
696+ logger .Printf ("CNS starting with no NC state, podInfoMap count %d" , len (podInfoByIP ))
697+ return types .Success
698+ }
699+
700+ // first step in reconciliation is to create all the NCs in CNS, no IP assignment yet.
701+ if returnCode := service .CreateNCs (ncReqs ); returnCode != types .Success {
702+ return returnCode
703+ }
704+
705+ logger .Debugf ("ncReqs created successfully, now save IPs" )
706+ // now reconcile IPAM state.
707+ if returnCode := service .ReconcileIPAssignment (podInfoByIP , ncReqs ); returnCode != types .Success {
708+ return returnCode
709+ }
710+
711+ if err := service .MarkExistingIPsAsPendingRelease (nnc .Spec .IPsNotInUse ); err != nil {
712+ logger .Errorf ("[Azure CNS] Error. Failed to mark IPs as pending %v" , nnc .Spec .IPsNotInUse )
713+ return types .UnexpectedError
714+ }
715+
716+ return types .Success
717+ }
718+
589719// IsCIDRSuperset returns true if newCIDR is a superset of oldCIDR (i.e., all IPs in oldCIDR are contained in newCIDR).
590720func validateCIDRSuperset (newCIDR , oldCIDR string ) bool {
591721 // Parse newCIDR and oldCIDR into netip.Prefix
0 commit comments