@@ -2,38 +2,42 @@ package restserver
22
33import (
44 "context"
5- "errors"
65 "fmt"
76 "net"
87 "net/http"
8+ "sort"
9+
10+ "github.com/pkg/errors"
911
1012 "github.com/Azure/azure-container-networking/cns"
1113 "github.com/Azure/azure-container-networking/cns/logger"
1214 "github.com/Azure/azure-container-networking/cns/types"
1315 "github.com/Azure/azure-container-networking/common"
14- "github.com/Azure/azure-container-networking/test/internal/kubernetes"
16+ "github.com/Azure/azure-container-networking/crd/multitenancy"
17+ "github.com/Azure/azure-container-networking/crd/multitenancy/api/v1alpha1"
1518 v1 "k8s.io/api/core/v1"
19+ apierrors "k8s.io/apimachinery/pkg/api/errors"
1620 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18- "k8s.io/apimachinery/pkg/runtime/schema"
1921 k8stypes "k8s.io/apimachinery/pkg/types"
20- "k8s.io/client-go/dynamic"
21- "k8s.io/client-go/rest"
22- "k8s.io/utils/pointer"
2322 "sigs.k8s.io/controller-runtime/pkg/client"
23+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2424)
2525
2626const (
2727 // NUMALabel is the label key used to indicate if a pod requires NUMA-aware IB device assignment
2828 NUMALabel = "numa-aware-ib-device-assignment"
2929 PodNetworkInstance = "pod-network-instance"
30+ PNILabel = "kubernetes.azure.com/pod-network-instance"
31+ fieldOwner = "requestcontroller"
3032)
3133
3234// assignIBDevicesToPod handles POST requests to assign IB devices to a pod
3335func (service * HTTPRestService ) assignIBDevicesToPod (w http.ResponseWriter , r * http.Request ) {
3436 opName := "assignIBDevicesToPod"
3537 var req cns.AssignIBDevicesToPodRequest
3638 var response cns.AssignIBDevicesToPodResponse
39+ ctx := context .Background ()
40+ pod := & v1.Pod {}
3741
3842 // Decode the request
3943 err := common .Decode (w , r , & req )
@@ -51,13 +55,14 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
5155 return
5256 }
5357
54- // Client-go/context stuff
55- ctx := context .Background ()
56- cli := kubernetes .MustGetClientset ()
58+ // Format the pod name and namespace into a k8s 'namespaced name'
59+ podNamespacedName := k8stypes.NamespacedName {
60+ Namespace : req .PodNamespace ,
61+ Name : req .PodName ,
62+ }
5763
58- // Get pod
59- pod , err := getPod (ctx , cli , req .PodName , req .PodNamespace )
60- if err != nil {
64+ // Get the pod
65+ if err := service .Client .Get (ctx , podNamespacedName , pod ); err != nil {
6166 response .Message = fmt .Sprintf ("Failed to get pod %s/%s: %v" , req .PodNamespace , req .PodName , err )
6267 respond (opName , w , http .StatusInternalServerError , types .UnexpectedError , response )
6368 return
@@ -71,7 +76,7 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
7176 return
7277 }
7378
74- // Check if the devices are unprogrammed
79+ // Check if the requested IB devices are unprogrammed
7580 for _ , ibMAC := range req .IBMACAddresses {
7681 if ! IBDeviceIsUnprogrammed (ibMAC ) {
7782 response .Message = fmt .Sprintf ("IB device with MAC address %s is not unprogrammed" , ibMAC )
@@ -80,8 +85,12 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
8085 }
8186 }
8287
83- // TODO: Create MTPNC with IB devices in spec
84- createMTPNC (pod , req .IBMACAddresses )
88+ // Create MTPNC with IB devices in spec
89+ if err = service .createMTPNC (ctx , pod , req .IBMACAddresses ); err != nil {
90+ response .Message = fmt .Sprintf ("Failed to create MTPNC for pod %s/%s: %v" , req .PodNamespace , req .PodName , err )
91+ respond (opName , w , http .StatusInternalServerError , types .UnexpectedError , response )
92+ return
93+ }
8594
8695 // Report back a successful assignment
8796 response .Message = fmt .Sprintf ("Successfully assigned %d IB devices to pod %s/%s" ,
@@ -141,50 +150,101 @@ func IBDeviceIsUnprogrammed(ibMAC net.HardwareAddr) bool {
141150 return true
142151}
143152
144- func createMTPNC (pod * v1.Pod , ibMACs []net.HardwareAddr ) error {
145- // Create in-cluster REST config since this code runs in a pod on a Kubernetes cluster
146- config , err := rest .InClusterConfig ()
147- if err != nil {
148- logger .Printf ("Failed to create in-cluster config: %v" , err )
149- return err
153+ func (service * HTTPRestService ) createMTPNC (ctx context.Context , pod * v1.Pod , ibMACs []net.HardwareAddr ) error {
154+ // create the MTPNC for the pod
155+ mtpnc := & v1alpha1.MultitenantPodNetworkConfig {
156+ ObjectMeta : metav1.ObjectMeta {
157+ Name : pod .Name ,
158+ Namespace : pod .Namespace ,
159+ },
160+ Spec : v1alpha1.MultitenantPodNetworkConfigSpec {
161+ PodNetworkInstance : pod .Labels [PNILabel ],
162+ },
150163 }
151164
152- dynamicClient , err := dynamic .NewForConfig (config )
153- if err != nil {
154- return err
165+ if err := controllerutil .SetControllerReference (pod , mtpnc , multitenancy .Scheme ); err != nil {
166+ return errors .Wrap (err , "unable to set controller reference for mtpnc" )
155167 }
156168
157- mtpnc := & unstructured.Unstructured {}
158- mtpnc .SetGroupVersionKind (schema.GroupVersionKind {
159- Group : "multitenancy.your.domain" , // replace with your CRD's group
160- Version : "v1alpha1" ,
161- Kind : "MultitenantPodNetworkConfig" ,
162- })
163- mtpnc .SetName (pod .Name )
164- mtpnc .SetNamespace (pod .Namespace )
165- mtpnc .Object ["spec" ] = map [string ]interface {}{
166- "podNetworkInstance" : pod .Labels ["podnetworkinstance" ], // adjust key as needed
169+ if createErr := service .Client .Create (ctx , mtpnc ); createErr != nil {
170+ // return any creation error except IsAlreadyExists
171+ if ! apierrors .IsAlreadyExists (createErr ) {
172+ return errors .Wrap (createErr , "error creating mtpnc" )
173+ }
174+
175+ existingMTPNC := & v1alpha1.MultitenantPodNetworkConfig {}
176+ if getErr := service .Client .Get (ctx , k8stypes.NamespacedName {Name : mtpnc .Name , Namespace : mtpnc .Namespace }, existingMTPNC ); getErr != nil {
177+ return errors .Wrap (getErr , "mtpnc already exists, but got error while reading it from apiserver" )
178+ }
179+
180+ // If the ownership or spec is wrong, try to patch it. We can't really support updates because once the MTPNC has an IP, we don't
181+ // take it away, but it's possible that the customer created a MTPNC manually and we don't want to get stuck if they did, so
182+ // we'll just make a best effort to keep the MTPNC up-to-date with the Pod.
183+ if patch , patchRequired := determineMTPNCUpdate (existingMTPNC , mtpnc ); patchRequired {
184+ if patchErr := service .Client .Patch (ctx , patch , client .Apply , client .ForceOwnership , client .FieldOwner (fieldOwner )); patchErr != nil {
185+ return errors .Wrap (patchErr , "mtpnc requires an update but got error while patching" )
186+ }
187+ service .Logger .Info (fmt .Sprintf ("Patched existing MTPNC %s/%s to match desired state" , mtpnc .Namespace , mtpnc .Name ))
188+ }
167189 }
190+ return nil
191+ }
168192
169- // Set owner reference
170- ownerRef := metav1.OwnerReference {
171- APIVersion : "v1" , // or your CRD's API version
172- Kind : "Pod" ,
173- Name : pod .Name ,
174- UID : pod .UID ,
175- Controller : pointer .BoolPtr (true ),
176- BlockOwnerDeletion : pointer .BoolPtr (true ),
193+ // determineMTPNCUpdate compares the ownership references and specs of the two MTPNC objects and returns a MTPNC for patching to the
194+ // desired state and true. If no update is required, this will return nil and false
195+ func determineMTPNCUpdate (existing , desired * v1alpha1.MultitenantPodNetworkConfig ) (* v1alpha1.MultitenantPodNetworkConfig , bool ) {
196+ patchRequired := false
197+ patchSkel := & v1alpha1.MultitenantPodNetworkConfig {
198+ ObjectMeta : metav1.ObjectMeta {
199+ Name : existing .Name ,
200+ Namespace : existing .Namespace ,
201+ },
177202 }
178- mtpnc .SetOwnerReferences ([]metav1.OwnerReference {ownerRef })
179203
180- // Create mtpnc using dynamic client
181- gvr := schema.GroupVersionResource {
182- Group : "multitenancy.your.domain" , // replace with your CRD's group
183- Version : "v1alpha1" ,
184- Resource : "multitenantpodnetworkconfigs" , // plural name of your CRD
204+ if ! ownerReferencesEqual (existing .OwnerReferences , desired .OwnerReferences ) {
205+ patchRequired = true
206+ patchSkel .OwnerReferences = desired .OwnerReferences
185207 }
186208
187- if _ , err := dynamicClient . Resource ( gvr ). Namespace ( mtpnc . GetNamespace ()). Create ( context . TODO (), mtpnc , metav1. CreateOptions {}); err != nil {
188- // handle error
209+ if patchRequired {
210+ return patchSkel , true
189211 }
212+
213+ return nil , false
214+ }
215+
216+ func ownerReferencesEqual (o1 , o2 []metav1.OwnerReference ) bool {
217+ if len (o1 ) != len (o2 ) {
218+ return false
219+ }
220+
221+ // sort the slices by UID
222+ sort .Slice (o1 , func (i , j int ) bool {
223+ return o1 [i ].UID < o1 [j ].UID
224+ })
225+ sort .Slice (o2 , func (i , j int ) bool {
226+ return o2 [i ].UID < o2 [j ].UID
227+ })
228+
229+ // compare each owner ref
230+ equal := true
231+ for i := range o1 {
232+ equal = equal &&
233+ o1 [i ].Kind == o2 [i ].Kind &&
234+ o1 [i ].Name == o2 [i ].Name &&
235+ o1 [i ].UID == o2 [i ].UID &&
236+ o1 [i ].APIVersion == o2 [i ].APIVersion &&
237+ boolPtrsEqual (o1 [i ].Controller , o2 [i ].Controller ) &&
238+ boolPtrsEqual (o1 [i ].BlockOwnerDeletion , o2 [i ].BlockOwnerDeletion )
239+ }
240+
241+ return equal
242+ }
243+
244+ func boolPtrsEqual (b1 , b2 * bool ) bool {
245+ if b1 == nil || b2 == nil {
246+ return b1 == b2
247+ }
248+
249+ return * b1 == * b2
190250}
0 commit comments