Skip to content

Commit b39f8dd

Browse files
committed
Creating the MTPNC
1 parent b7ecb50 commit b39f8dd

File tree

1 file changed

+111
-51
lines changed

1 file changed

+111
-51
lines changed

cns/restserver/ibdevices.go

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,42 @@ package restserver
22

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

2626
const (
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
3335
func (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

Comments
 (0)