Skip to content

Commit a51f3ea

Browse files
committed
Just poc to talk about
1 parent 6f21b7a commit a51f3ea

File tree

1 file changed

+144
-11
lines changed

1 file changed

+144
-11
lines changed

cns/restserver/ibdevices.go

Lines changed: 144 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
package restserver
22

33
import (
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

4892
func 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

Comments
 (0)