Skip to content

Commit ac0511b

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 0fb0e8a commit ac0511b

File tree

5 files changed

+264
-17
lines changed

5 files changed

+264
-17
lines changed
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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, networkSelectionElement *nettypes.NetworkSelectionElement) (string, error) {
41+
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
42+
if err != nil {
43+
return "", err
44+
}
45+
46+
netName := NamespacedName(networkSelectionElement.Namespace, networkSelectionElement.Name)
47+
var newIfaceStatus []nettypes.NetworkStatus
48+
newIfaceStatus = make([]nettypes.NetworkStatus, 0)
49+
for i := range currentIfaceStatus {
50+
if currentIfaceStatus[i].Name == netName && currentIfaceStatus[i].Interface == networkSelectionElement.InterfaceRequest {
51+
continue
52+
}
53+
newIfaceStatus = append(newIfaceStatus, currentIfaceStatus[i])
54+
}
55+
56+
newIfaceString, err := json.Marshal(newIfaceStatus)
57+
if err != nil {
58+
return "", fmt.Errorf("failed to marshall the dynamic networks status after deleting interface")
59+
}
60+
return string(newIfaceString), nil
61+
}
62+
63+
func podDynamicNetworkStatus(currentPod *corev1.Pod) ([]nettypes.NetworkStatus, error) {
64+
var currentIfaceStatus []nettypes.NetworkStatus
65+
if currentIfaceStatusString, wasFound := currentPod.Annotations[nettypes.NetworkStatusAnnot]; wasFound {
66+
if err := json.Unmarshal([]byte(currentIfaceStatusString), &currentIfaceStatus); err != nil {
67+
return nil, fmt.Errorf("could not unmarshall the current dynamic annotations for pod %s: %v", podNameAndNs(currentPod), err)
68+
}
69+
}
70+
return currentIfaceStatus, nil
71+
}
72+
73+
func podNameAndNs(currentPod *corev1.Pod) string {
74+
return fmt.Sprintf("%s/%s", currentPod.GetNamespace(), currentPod.GetName())
75+
}
76+
77+
func NamespacedName(podNamespace string, podName string) string {
78+
return fmt.Sprintf("%s/%s", podNamespace, podName)
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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, ifaceToRemove, expectedNetworkStatus string) {
59+
Expect(
60+
DeleteDynamicIfaceFromStatus(
61+
newPod(podName, namespace, initialNetStatus...),
62+
newNetworkSelectionElementWithIface(networkName, ifaceToRemove, namespace),
63+
),
64+
).To(Equal(expectedNetworkStatus))
65+
},
66+
Entry("when there aren't any existing interfaces", nil, "net1", "iface1", "[]"),
67+
Entry("when we remove all the currently existing interfaces", []nadv1.NetworkStatus{
68+
{
69+
Name: NamespacedName(namespace, networkName),
70+
Interface: "iface1",
71+
Mac: "00:00:00:20:10:00",
72+
}}, networkName, "iface1", "[]"),
73+
Entry("when there is *not* a matching interface to remove", []nadv1.NetworkStatus{
74+
{
75+
Name: NamespacedName(namespace, networkName),
76+
Interface: "iface1",
77+
Mac: "00:00:00:20:10:00",
78+
}}, "net2", "iface1", `[{"name":"ns1/tenantnetwork","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`),
79+
Entry("when we remove one of the existing interfaces", []nadv1.NetworkStatus{
80+
{
81+
Name: NamespacedName(namespace, networkName),
82+
Interface: "iface1",
83+
Mac: "00:00:00:20:10:00",
84+
},
85+
{
86+
Name: NamespacedName(namespace, "net2"),
87+
Interface: "iface2",
88+
Mac: "aa:bb:cc:20:10:00",
89+
},
90+
}, "net2", "iface2", `[{"name":"ns1/tenantnetwork","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`))
91+
})
92+
93+
func newPod(podName string, namespace string, netStatus ...nadv1.NetworkStatus) *corev1.Pod {
94+
status, err := json.Marshal(netStatus)
95+
if err != nil {
96+
return nil
97+
}
98+
return &corev1.Pod{
99+
ObjectMeta: metav1.ObjectMeta{
100+
Name: podName,
101+
Namespace: namespace,
102+
Annotations: map[string]string{
103+
nadv1.NetworkStatusAnnot: string(status),
104+
},
105+
},
106+
}
107+
}
108+
109+
func newResponse(ifaceName string, macAddr string, ips ...string) *api.Response {
110+
var ipConfs []*cni100.IPConfig
111+
for i := range ips {
112+
ipConfs = append(ipConfs, &cni100.IPConfig{Address: *ipNet(ips[i])})
113+
}
114+
115+
const sandboxPath = "/over/there"
116+
ifaces := []*cni100.Interface{{
117+
Name: ifaceName,
118+
Mac: macAddr,
119+
Sandbox: sandboxPath,
120+
}}
121+
return &api.Response{
122+
Result: &cni100.Result{
123+
CNIVersion: "1.0.0",
124+
Interfaces: ifaces,
125+
IPs: ipConfs,
126+
}}
127+
}
128+
129+
func ipNet(ipString string) *net.IPNet {
130+
ip, network, err := net.ParseCIDR(ipString)
131+
if err != nil {
132+
return nil
133+
}
134+
network.IP = ip
135+
return network
136+
}

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

+39-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,34 @@ func (pnc *PodNetworksController) removeNetworks(dynamicAttachmentRequest *Dynam
307314
}
308315
klog.Infof("response: %v", *response)
309316

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

313333
return nil
314334
}
315335

336+
func (pnc *PodNetworksController) updatePodNetworkStatus(pod *corev1.Pod, newIfaceStatus string) error {
337+
pod.Annotations[nadv1.NetworkStatusAnnot] = newIfaceStatus
338+
339+
if _, err := pnc.k8sClientSet.CoreV1().Pods(pod.GetNamespace()).Update(context.Background(), pod, metav1.UpdateOptions{}); err != nil {
340+
return fmt.Errorf("failed to update pod's network-status annotations for %s: %v", pod.GetName(), err)
341+
}
342+
return nil
343+
}
344+
316345
func networkSelectionElements(podAnnotations map[string]string, podNamespace string) ([]*nadv1.NetworkSelectionElement, error) {
317346
podNetworks, ok := podAnnotations[nadv1.NetworkAttachmentAnnot]
318347
if !ok {
@@ -408,7 +437,7 @@ func podContainerID(pod *corev1.Pod) string {
408437
func addIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement) string {
409438
return fmt.Sprintf(
410439
"pod [%s]: added interface %s to network: %s",
411-
namespacedName(pod.GetNamespace(), pod.GetName()),
440+
annotations.NamespacedName(pod.GetNamespace(), pod.GetName()),
412441
network.InterfaceRequest,
413442
network.Name,
414443
)
@@ -417,7 +446,7 @@ func addIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement
417446
func removeIfaceEventFormat(pod *corev1.Pod, network *nadv1.NetworkSelectionElement) string {
418447
return fmt.Sprintf(
419448
"pod [%s]: removed interface %s from network: %s",
420-
namespacedName(pod.GetNamespace(), pod.GetName()),
449+
annotations.NamespacedName(pod.GetNamespace(), pod.GetName()),
421450
network.InterfaceRequest,
422451
network.Name,
423452
)

0 commit comments

Comments
 (0)