@@ -2,38 +2,42 @@ package restserver
2
2
3
3
import (
4
4
"context"
5
- "errors"
6
5
"fmt"
7
6
"net"
8
7
"net/http"
8
+ "sort"
9
+
10
+ "github.com/pkg/errors"
9
11
10
12
"github.com/Azure/azure-container-networking/cns"
11
13
"github.com/Azure/azure-container-networking/cns/logger"
12
14
"github.com/Azure/azure-container-networking/cns/types"
13
15
"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"
15
18
v1 "k8s.io/api/core/v1"
19
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
16
20
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
21
k8stypes "k8s.io/apimachinery/pkg/types"
20
- "k8s.io/client-go/dynamic"
21
- "k8s.io/client-go/rest"
22
- "k8s.io/utils/pointer"
23
22
"sigs.k8s.io/controller-runtime/pkg/client"
23
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
24
24
)
25
25
26
26
const (
27
27
// NUMALabel is the label key used to indicate if a pod requires NUMA-aware IB device assignment
28
28
NUMALabel = "numa-aware-ib-device-assignment"
29
29
PodNetworkInstance = "pod-network-instance"
30
+ PNILabel = "kubernetes.azure.com/pod-network-instance"
31
+ fieldOwner = "requestcontroller"
30
32
)
31
33
32
34
// assignIBDevicesToPod handles POST requests to assign IB devices to a pod
33
35
func (service * HTTPRestService ) assignIBDevicesToPod (w http.ResponseWriter , r * http.Request ) {
34
36
opName := "assignIBDevicesToPod"
35
37
var req cns.AssignIBDevicesToPodRequest
36
38
var response cns.AssignIBDevicesToPodResponse
39
+ ctx := context .Background ()
40
+ pod := & v1.Pod {}
37
41
38
42
// Decode the request
39
43
err := common .Decode (w , r , & req )
@@ -51,13 +55,14 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
51
55
return
52
56
}
53
57
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
+ }
57
63
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 {
61
66
response .Message = fmt .Sprintf ("Failed to get pod %s/%s: %v" , req .PodNamespace , req .PodName , err )
62
67
respond (opName , w , http .StatusInternalServerError , types .UnexpectedError , response )
63
68
return
@@ -71,7 +76,7 @@ func (service *HTTPRestService) assignIBDevicesToPod(w http.ResponseWriter, r *h
71
76
return
72
77
}
73
78
74
- // Check if the devices are unprogrammed
79
+ // Check if the requested IB devices are unprogrammed
75
80
for _ , ibMAC := range req .IBMACAddresses {
76
81
if ! IBDeviceIsUnprogrammed (ibMAC ) {
77
82
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
80
85
}
81
86
}
82
87
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
+ }
85
94
86
95
// Report back a successful assignment
87
96
response .Message = fmt .Sprintf ("Successfully assigned %d IB devices to pod %s/%s" ,
@@ -141,50 +150,101 @@ func IBDeviceIsUnprogrammed(ibMAC net.HardwareAddr) bool {
141
150
return true
142
151
}
143
152
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
+ },
150
163
}
151
164
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" )
155
167
}
156
168
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
+ }
167
189
}
190
+ return nil
191
+ }
168
192
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
+ },
177
202
}
178
- mtpnc .SetOwnerReferences ([]metav1.OwnerReference {ownerRef })
179
203
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
185
207
}
186
208
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
189
211
}
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
190
250
}
0 commit comments