11package restserver
22
33import (
4+ "context"
5+ "errors"
46 "fmt"
7+ "net"
58 "net/http"
69
710 "github.com/Azure/azure-container-networking/cns"
811 "github.com/Azure/azure-container-networking/cns/logger"
912 "github.com/Azure/azure-container-networking/cns/types"
1013 "github.com/Azure/azure-container-networking/common"
14+ "github.com/Azure/azure-container-networking/test/internal/kubernetes"
15+ v1 "k8s.io/api/core/v1"
16+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+ "k8s.io/apimachinery/pkg/runtime/schema"
19+ k8stypes "k8s.io/apimachinery/pkg/types"
20+ "k8s.io/client-go/dynamic"
21+ "k8s.io/client-go/rest"
22+ "k8s.io/utils/pointer"
23+ "sigs.k8s.io/controller-runtime/pkg/client"
24+ )
25+
26+ const (
27+ // NUMALabel is the label key used to indicate if a pod requires NUMA-aware IB device assignment
28+ NUMALabel = "numa-aware-ib-device-assignment"
29+ PodNetworkInstance = "pod-network-instance"
1130)
1231
1332// assignIBDevicesToPod handles POST requests to assign IB devices to a pod
@@ -20,29 +39,54 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
2039 err := common .Decode (w , r , & req )
2140 logger .Request (service .Name , & req , err )
2241 if err != nil {
42+ response .Message = fmt .Sprintf ("Failed to decode request: %v" , err )
43+ respond (opName , w , http .StatusBadRequest , types .InvalidRequest , response )
2344 return
2445 }
2546
2647 // Validate the request
2748 if err := validateAssignIBDevicesRequest (req ); err != nil {
2849 response .Message = fmt .Sprintf ("Invalid request: %v" , err )
29- w .WriteHeader (http .StatusBadRequest )
30- err = common .Encode (w , & response )
31- logger .Response (opName , response , types .InvalidRequest , err )
50+ respond (opName , w , http .StatusBadRequest , types .InvalidRequest , response )
3251 return
3352 }
3453
35- // TODO: Check that the pod exists
36- // TODO: Check that the IB devices are "unprogrammed" (i.e. available)
37- // TODO: Create MTPNC with IB devices in spec (and update cache)
54+ // Client-go/context stuff
55+ ctx := context .Background ()
56+ cli := kubernetes .MustGetClientset ()
57+
58+ // Get pod
59+ pod , err := getPod (ctx , cli , req .PodName , req .PodNamespace )
60+ if err != nil {
61+ response .Message = fmt .Sprintf ("Failed to get pod %s/%s: %v" , req .PodNamespace , req .PodName , err )
62+ respond (opName , w , http .StatusInternalServerError , types .UnexpectedError , response )
63+ return
64+ }
65+
66+ // Check that the pod has the NUMA label
67+ if ! podHasNUMALabel (pod ) {
68+ response .Message = fmt .Sprintf ("Pod %s/%s does not have the required NUMA label %s" ,
69+ req .PodNamespace , req .PodName , NUMALabel )
70+ respond (opName , w , http .StatusBadRequest , types .InvalidRequest , response )
71+ return
72+ }
73+
74+ // Check if the devices are unprogrammed
75+ for _ , ibMAC := range req .IBMACAddresses {
76+ if ! IBDeviceIsUnprogrammed (ibMAC ) {
77+ response .Message = fmt .Sprintf ("IB device with MAC address %s is not unprogrammed" , ibMAC )
78+ respond (opName , w , http .StatusBadRequest , types .AddressUnavailable , response )
79+ return
80+ }
81+ }
82+
83+ // TODO: Create MTPNC with IB devices in spec
84+ createMTPNC (pod , req .IBMACAddresses )
3885
3986 // Report back a successful assignment
4087 response .Message = fmt .Sprintf ("Successfully assigned %d IB devices to pod %s/%s" ,
4188 len (req .IBMACAddresses ), req .PodNamespace , req .PodName )
42-
43- w .WriteHeader (http .StatusOK )
44- err = common .Encode (w , & response )
45- logger .Response (opName , response , types .Success , err )
89+ respond (opName , w , http .StatusOK , types .Success , response )
4690}
4791
4892func validateAssignIBDevicesRequest (req cns.AssignIBDevicesToPodRequest ) error {
@@ -52,6 +96,95 @@ func validateAssignIBDevicesRequest(req cns.AssignIBDevicesToPodRequest) error {
5296 if len (req .IBMACAddresses ) == 0 {
5397 return fmt .Errorf ("at least one IB MAC address is required" )
5498 }
55- // TODO Make sure that the given MAC is valid too
99+ // Validate MAC address format - since they're already net.HardwareAddr, they should be valid
100+ for _ , hwAddr := range req .IBMACAddresses {
101+ if len (hwAddr ) == 0 {
102+ return fmt .Errorf ("invalid empty MAC address" )
103+ }
104+ }
56105 return nil
57106}
107+
108+ func respond (opName string , w http.ResponseWriter , httpStatusCode int , cnsCode types.ResponseCode , response interface {}) {
109+ w .WriteHeader (httpStatusCode )
110+ _ = common .Encode (w , & response )
111+ logger .Response (opName , response , cnsCode , errors .New (fmt .Sprintf ("HTTP: %v CNSCode:%v Response: %v" , httpStatusCode , cnsCode , response )))
112+ }
113+
114+ func getPod (ctx context.Context , k8sClient client.Client , podName , podNamespace string ) (* v1.Pod , error ) {
115+ // Create a NamespacedName for the pod
116+ podNamespacedName := k8stypes.NamespacedName {
117+ Namespace : podNamespace ,
118+ Name : podName ,
119+ }
120+
121+ // Try to get the pod from the cluster
122+ pod := & v1.Pod {}
123+ if err := k8sClient .Get (ctx , podNamespacedName , pod ); err != nil {
124+ return nil , err
125+ }
126+
127+ return pod , nil
128+ }
129+ func podHasNUMALabel (pod * v1.Pod ) bool {
130+ if pod == nil || pod .Labels == nil {
131+ return false
132+ }
133+ _ , hasLabel := pod .Labels [NUMALabel ]
134+ return hasLabel
135+ }
136+
137+ // TODO: Finish this
138+ func IBDeviceIsUnprogrammed (ibMAC net.HardwareAddr ) bool {
139+ // Check if the IB device is available (i.e., not assigned to any pod)
140+ // This is a placeholder implementation and should be replaced with actual logic
141+ return true
142+ }
143+
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
150+ }
151+
152+ dynamicClient , err := dynamic .NewForConfig (config )
153+ if err != nil {
154+ return err
155+ }
156+
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
167+ }
168+
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 ),
177+ }
178+ mtpnc .SetOwnerReferences ([]metav1.OwnerReference {ownerRef })
179+
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
185+ }
186+
187+ if _ , err := dynamicClient .Resource (gvr ).Namespace (mtpnc .GetNamespace ()).Create (context .TODO (), mtpnc , metav1.CreateOptions {}); err != nil {
188+ // handle error
189+ }
190+ }
0 commit comments