Skip to content

Commit 050175a

Browse files
committed
controller, network status: update pod after interface add/remove
This commit updates the pods network-status annotation whenever an interface is hot-plugged to the pod (or removed from it ...). Signed-off-by: Miguel Duarte Barroso <[email protected]>
1 parent 76832b6 commit 050175a

File tree

5 files changed

+268
-17
lines changed

5 files changed

+268
-17
lines changed
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package annotations
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
9+
nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
10+
nadutils "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils"
11+
multusapi "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/server/api"
12+
)
13+
14+
func AddDynamicIfaceToStatus(currentPod *corev1.Pod, networkSelectionElement *nettypes.NetworkSelectionElement, response *multusapi.Response) (string, error) {
15+
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
16+
if err != nil {
17+
return "", err
18+
}
19+
20+
if response != nil && response.Result != nil {
21+
newIfaceStatus, err := nadutils.CreateNetworkStatus(
22+
response.Result,
23+
NamespacedName(networkSelectionElement.Namespace, networkSelectionElement.Name),
24+
false,
25+
nil,
26+
)
27+
if err != nil {
28+
return "", fmt.Errorf("failed to create NetworkStatus from the response: %v", err)
29+
}
30+
31+
newIfaceString, err := json.Marshal(append(currentIfaceStatus, *newIfaceStatus))
32+
if err != nil {
33+
return "", fmt.Errorf("failed to marshall the dynamic networks status after interface creation")
34+
}
35+
return string(newIfaceString), nil
36+
}
37+
return "", fmt.Errorf("got an empty response from multus: %+v", response)
38+
}
39+
40+
func DeleteDynamicIfaceFromStatus(currentPod *corev1.Pod, netName string, ifaceName string) (string, error) {
41+
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
42+
if err != nil {
43+
return "", err
44+
}
45+
46+
var newIfaceStatus []nettypes.NetworkStatus
47+
newIfaceStatus = make([]nettypes.NetworkStatus, 0)
48+
for i := range currentIfaceStatus {
49+
if currentIfaceStatus[i].Name == netName && currentIfaceStatus[i].Interface == ifaceName {
50+
continue
51+
}
52+
newIfaceStatus = append(newIfaceStatus, currentIfaceStatus[i])
53+
}
54+
55+
newIfaceString, err := json.Marshal(newIfaceStatus)
56+
if err != nil {
57+
return "", fmt.Errorf("failed to marshall the dynamic networks status after deleting interface")
58+
}
59+
return string(newIfaceString), nil
60+
}
61+
62+
func podDynamicNetworkStatus(currentPod *corev1.Pod) ([]nettypes.NetworkStatus, error) {
63+
var currentIfaceStatus []nettypes.NetworkStatus
64+
if currentIfaceStatusString, wasFound := currentPod.Annotations[nettypes.NetworkStatusAnnot]; wasFound {
65+
if err := json.Unmarshal([]byte(currentIfaceStatusString), &currentIfaceStatus); err != nil {
66+
return nil, fmt.Errorf("could not unmarshall the current dynamic annotations for pod %s: %v", podNameAndNs(currentPod), err)
67+
}
68+
}
69+
return currentIfaceStatus, nil
70+
}
71+
72+
func podNameAndNs(currentPod *corev1.Pod) string {
73+
return fmt.Sprintf("%s/%s", currentPod.GetNamespace(), currentPod.GetName())
74+
}
75+
76+
func NamespacedName(podNamespace string, podName string) string {
77+
return fmt.Sprintf("%s/%s", podNamespace, podName)
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package annotations
2+
3+
import (
4+
"encoding/json"
5+
"net"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
10+
cni100 "github.com/containernetworking/cni/pkg/types/100"
11+
12+
corev1 "k8s.io/api/core/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
15+
nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
16+
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/server/api"
17+
)
18+
19+
var _ = Describe("NetworkStatusFromResponse", func() {
20+
const (
21+
ifaceName = "ens32"
22+
namespace = "ns1"
23+
networkName = "tenantnetwork"
24+
podName = "tpod"
25+
)
26+
27+
DescribeTable("add dynamic interface to network status", func(initialNetStatus []nadv1.NetworkStatus, resultIPs []string, expectedNetworkStatus string) {
28+
const (
29+
ifaceToAdd = "newiface"
30+
macAddr = "02:03:04:05:06:07"
31+
)
32+
Expect(
33+
AddDynamicIfaceToStatus(
34+
newPod(podName, namespace, initialNetStatus...),
35+
newNetworkSelectionElementWithIface(networkName, ifaceName, namespace),
36+
newResponse(ifaceToAdd, macAddr, resultIPs...),
37+
),
38+
).To(Equal(expectedNetworkStatus))
39+
},
40+
Entry("initial empty pod", []nadv1.NetworkStatus{}, nil, `[{"name":"ns1/tenantnetwork","interface":"newiface","mac":"02:03:04:05:06:07","dns":{}}]`),
41+
Entry("pod with a network present in the network status", []nadv1.NetworkStatus{
42+
{
43+
Name: "net1",
44+
Interface: "iface1",
45+
Mac: "00:00:00:20:10:00",
46+
}},
47+
nil,
48+
`[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}},{"name":"ns1/tenantnetwork","interface":"newiface","mac":"02:03:04:05:06:07","dns":{}}]`),
49+
Entry("result with IPs", []nadv1.NetworkStatus{
50+
{
51+
Name: "net1",
52+
Interface: "iface1",
53+
Mac: "00:00:00:20:10:00",
54+
}},
55+
[]string{"10.10.10.10/24"},
56+
`[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}},{"name":"ns1/tenantnetwork","interface":"newiface","ips":["10.10.10.10"],"mac":"02:03:04:05:06:07","dns":{}}]`))
57+
58+
DescribeTable("remove an interface to the current network status", func(initialNetStatus []nadv1.NetworkStatus, networkName, ifaceName, expectedNetworkStatus string) {
59+
Expect(
60+
DeleteDynamicIfaceFromStatus(
61+
newPod(podName, namespace, initialNetStatus...),
62+
networkName,
63+
ifaceName,
64+
),
65+
).To(Equal(expectedNetworkStatus))
66+
},
67+
Entry("when there aren't any existing interfaces", nil, "net1", "iface1", "[]"),
68+
Entry("when we remove all the currently existing interfaces", []nadv1.NetworkStatus{
69+
{
70+
Name: "net1",
71+
Interface: "iface1",
72+
Mac: "00:00:00:20:10:00",
73+
}}, "net1", "iface1", "[]"),
74+
Entry("when there is *not* a matching interface to remove", []nadv1.NetworkStatus{
75+
{
76+
Name: "net1",
77+
Interface: "iface1",
78+
Mac: "00:00:00:20:10:00",
79+
}}, "net2", "iface1", `[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`),
80+
Entry("when we remove one of the existing interfaces", []nadv1.NetworkStatus{
81+
{
82+
Name: "net1",
83+
Interface: "iface1",
84+
Mac: "00:00:00:20:10:00",
85+
},
86+
{
87+
Name: "net2",
88+
Interface: "iface2",
89+
Mac: "aa:bb:cc:20:10:00",
90+
},
91+
}, "net2", "iface2", `[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`))
92+
})
93+
94+
func newPod(podName string, namespace string, netStatus ...nadv1.NetworkStatus) *corev1.Pod {
95+
status, err := json.Marshal(netStatus)
96+
if err != nil {
97+
return nil
98+
}
99+
return &corev1.Pod{
100+
ObjectMeta: metav1.ObjectMeta{
101+
Name: podName,
102+
Namespace: namespace,
103+
Annotations: map[string]string{
104+
nadv1.NetworkStatusAnnot: string(status),
105+
},
106+
},
107+
}
108+
}
109+
110+
func newResponse(ifaceName string, macAddr string, ips ...string) *api.Response {
111+
var ipConfs []*cni100.IPConfig
112+
for i := range ips {
113+
ipConfs = append(ipConfs, &cni100.IPConfig{Address: *ipNet(ips[i])})
114+
}
115+
116+
const sandboxPath = "/over/there"
117+
ifaces := []*cni100.Interface{{
118+
Name: ifaceName,
119+
Mac: macAddr,
120+
Sandbox: sandboxPath,
121+
}}
122+
return &api.Response{
123+
Result: &cni100.Result{
124+
CNIVersion: "1.0.0",
125+
Interfaces: ifaces,
126+
IPs: ipConfs,
127+
}}
128+
}
129+
130+
func ipNet(ipString string) *net.IPNet {
131+
ip, network, err := net.ParseCIDR(ipString)
132+
if err != nil {
133+
return nil
134+
}
135+
network.IP = ip
136+
return network
137+
}

pkg/annotations/network-selection-elements_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package annotations
22

33
import (
4-
v1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
5-
. "github.com/onsi/ginkgo"
6-
. "github.com/onsi/gomega"
74
"strings"
85
"testing"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
10+
v1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
911
)
1012

1113
func TestController(t *testing.T) {

pkg/controller/pod.go

+43-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package controller
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"reflect"
78
"strings"
89
"time"
910

1011
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1113
"k8s.io/apimachinery/pkg/runtime"
1214
"k8s.io/apimachinery/pkg/util/wait"
1315
v1coreinformerfactory "k8s.io/client-go/informers"
@@ -174,7 +176,7 @@ func (pnc *PodNetworksController) handleResult(err error, dynamicAttachmentReque
174176

175177
currentRetries := pnc.workqueue.NumRequeues(dynamicAttachmentRequest)
176178
if currentRetries <= maxRetries {
177-
klog.Errorf("re-queued request for: %v", dynamicAttachmentRequest)
179+
klog.Errorf("re-queued request for: %v. Error: %v", dynamicAttachmentRequest, err)
178180
pnc.workqueue.AddRateLimited(dynamicAttachmentRequest)
179181
return
180182
}
@@ -196,7 +198,7 @@ func (pnc *PodNetworksController) handlePodUpdate(oldObj interface{}, newObj int
196198
}
197199
podNamespace := oldPod.GetNamespace()
198200
podName := oldPod.GetName()
199-
klog.V(logging.Debug).Infof("pod [%s] updated", namespacedName(podNamespace, podName))
201+
klog.V(logging.Debug).Infof("pod [%s] updated", annotations.NamespacedName(podNamespace, podName))
200202

201203
oldNetworkSelectionElements, err := networkSelectionElements(oldPod.Annotations, podNamespace)
202204
if err != nil {
@@ -211,7 +213,7 @@ func (pnc *PodNetworksController) handlePodUpdate(oldObj interface{}, newObj int
211213
}
212214

213215
toAdd := exclusiveNetworks(newNetworkSelectionElements, oldNetworkSelectionElements)
214-
klog.Infof("%d attachments to add to pod %s", len(toAdd), namespacedName(podNamespace, podName))
216+
klog.Infof("%d attachments to add to pod %s", len(toAdd), annotations.NamespacedName(podNamespace, podName))
215217

216218
netnsPath, err := pnc.netnsPath(newPod)
217219
if err != nil {
@@ -230,7 +232,7 @@ func (pnc *PodNetworksController) handlePodUpdate(oldObj interface{}, newObj int
230232
}
231233

232234
toRemove := exclusiveNetworks(oldNetworkSelectionElements, newNetworkSelectionElements)
233-
klog.Infof("%d attachments to remove from pod %s", len(toRemove), namespacedName(podNamespace, podName))
235+
klog.Infof("%d attachments to remove from pod %s", len(toRemove), annotations.NamespacedName(podNamespace, podName))
234236
if len(toRemove) > 0 {
235237
pnc.workqueue.Add(
236238
&DynamicAttachmentRequest{
@@ -243,10 +245,6 @@ func (pnc *PodNetworksController) handlePodUpdate(oldObj interface{}, newObj int
243245
}
244246
}
245247

246-
func namespacedName(podNamespace string, podName string) string {
247-
return fmt.Sprintf("%s/%s", podNamespace, podName)
248-
}
249-
250248
func (pnc *PodNetworksController) addNetworks(dynamicAttachmentRequest *DynamicAttachmentRequest, pod *corev1.Pod) error {
251249
for i := range dynamicAttachmentRequest.AttachmentNames {
252250
netToAdd := dynamicAttachmentRequest.AttachmentNames[i]
@@ -274,6 +272,15 @@ func (pnc *PodNetworksController) addNetworks(dynamicAttachmentRequest *DynamicA
274272
}
275273
klog.Infof("response: %v", *response.Result)
276274

275+
newIfaceStatus, err := annotations.AddDynamicIfaceToStatus(pod, netToAdd, response)
276+
if err != nil {
277+
return fmt.Errorf("failed to compute the updated network status: %v", err)
278+
}
279+
280+
if err := pnc.updatePodNetworkStatus(pod, newIfaceStatus); err != nil {
281+
return err
282+
}
283+
277284
pnc.Eventf(pod, corev1.EventTypeNormal, "AddedInterface", addIfaceEventFormat(pod, netToAdd))
278285
}
279286

@@ -307,12 +314,38 @@ func (pnc *PodNetworksController) removeNetworks(dynamicAttachmentRequest *Dynam
307314
}
308315
klog.Infof("response: %v", *response)
309316

317+
newIfaceStatus, err := annotations.DeleteDynamicIfaceFromStatus(
318+
pod,
319+
annotations.NamespacedName(netToRemove.Namespace, netToRemove.Name),
320+
netToRemove.InterfaceRequest,
321+
)
322+
if err != nil {
323+
return fmt.Errorf(
324+
"failed to compute the dynamic network attachments after deleting network: %s, iface: %s: %v",
325+
netToRemove.Name,
326+
netToRemove.InterfaceRequest,
327+
err,
328+
)
329+
}
330+
if err := pnc.updatePodNetworkStatus(pod, newIfaceStatus); err != nil {
331+
return err
332+
}
333+
310334
pnc.Eventf(pod, corev1.EventTypeNormal, "RemovedInterface", removeIfaceEventFormat(pod, netToRemove))
311335
}
312336

313337
return nil
314338
}
315339

340+
func (pnc *PodNetworksController) updatePodNetworkStatus(pod *corev1.Pod, newIfaceStatus string) error {
341+
pod.Annotations[nadv1.NetworkStatusAnnot] = newIfaceStatus
342+
343+
if _, err := pnc.k8sClientSet.CoreV1().Pods(pod.GetNamespace()).Update(context.Background(), pod, metav1.UpdateOptions{}); err != nil {
344+
return fmt.Errorf("failed to update pod's network-status annotations for %s: %v", pod.GetName(), err)
345+
}
346+
return nil
347+
}
348+
316349
func networkSelectionElements(podAnnotations map[string]string, podNamespace string) ([]*nadv1.NetworkSelectionElement, error) {
317350
podNetworks, ok := podAnnotations[nadv1.NetworkAttachmentAnnot]
318351
if !ok {
@@ -408,7 +441,7 @@ func podContainerID(pod *corev1.Pod) string {
408441
func addIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement) string {
409442
return fmt.Sprintf(
410443
"pod [%s]: added interface %s to network: %s",
411-
namespacedName(pod.GetNamespace(), pod.GetName()),
444+
annotations.NamespacedName(pod.GetNamespace(), pod.GetName()),
412445
network.InterfaceRequest,
413446
network.Name,
414447
)
@@ -417,7 +450,7 @@ func addIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement
417450
func removeIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement) string {
418451
return fmt.Sprintf(
419452
"pod [%s]: removed interface %s from network: %s",
420-
namespacedName(pod.GetNamespace(), pod.GetName()),
453+
annotations.NamespacedName(pod.GetNamespace(), pod.GetName()),
421454
network.InterfaceRequest,
422455
network.Name,
423456
)

0 commit comments

Comments
 (0)