Skip to content

Commit 62860a5

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 74eb154 commit 62860a5

File tree

4 files changed

+249
-3
lines changed

4 files changed

+249
-3
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+
multusapi "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/server/api"
11+
)
12+
13+
func AddDynamicIfaceToStatus(currentPod *corev1.Pod, networkSelectionElement *nettypes.NetworkSelectionElement, response *multusapi.Response) (string, error) {
14+
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
15+
if err != nil {
16+
return "", err
17+
}
18+
19+
newIfaceStatus := nettypes.NetworkStatus{
20+
Name: fmt.Sprintf("%s/%s", networkSelectionElement.Namespace, networkSelectionElement.Name),
21+
}
22+
23+
if response != nil && response.Result != nil {
24+
for _, iface := range response.Result.Interfaces {
25+
newIfaceStatus.Interface = iface.Name
26+
newIfaceStatus.Mac = iface.Mac
27+
}
28+
29+
var ips []string
30+
for _, ifaceIPConfig := range response.Result.IPs {
31+
ips = append(ips, ifaceIPConfig.Address.String())
32+
}
33+
if len(ips) > 0 {
34+
newIfaceStatus.IPs = ips
35+
}
36+
}
37+
38+
newIfaceString, err := json.Marshal(append(currentIfaceStatus, newIfaceStatus))
39+
if err != nil {
40+
return "", fmt.Errorf("failed to marshall the dynamic networks status")
41+
}
42+
return string(newIfaceString), nil
43+
}
44+
45+
func DeleteDynamicIfaceFromStatus(currentPod *corev1.Pod, netName string, ifaceName string) (string, error) {
46+
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
47+
if err != nil {
48+
return "", err
49+
}
50+
51+
var newIfaceStatus []nettypes.NetworkStatus
52+
newIfaceStatus = make([]nettypes.NetworkStatus, 0)
53+
for i := range currentIfaceStatus {
54+
if currentIfaceStatus[i].Name == netName && currentIfaceStatus[i].Interface == ifaceName {
55+
continue
56+
}
57+
newIfaceStatus = append(newIfaceStatus, currentIfaceStatus[i])
58+
}
59+
60+
newIfaceString, err := json.Marshal(newIfaceStatus)
61+
if err != nil {
62+
return "", fmt.Errorf("failed to marshall the dynamic networks status")
63+
}
64+
return string(newIfaceString), nil
65+
}
66+
67+
func podDynamicNetworkStatus(currentPod *corev1.Pod) ([]nettypes.NetworkStatus, error) {
68+
var currentIfaceStatus []nettypes.NetworkStatus
69+
if currentIfaceStatusString, wasFound := currentPod.Annotations[nettypes.NetworkStatusAnnot]; wasFound {
70+
if err := json.Unmarshal([]byte(currentIfaceStatusString), &currentIfaceStatus); err != nil {
71+
return nil, fmt.Errorf("could not unmarshall the current dynamic annotations for pod %s: %v", podNameAndNs(currentPod), err)
72+
}
73+
}
74+
return currentIfaceStatus, nil
75+
}
76+
77+
func podNameAndNs(currentPod *corev1.Pod) string {
78+
return fmt.Sprintf("%s/%s", currentPod.GetNamespace(), currentPod.GetName())
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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+
Expect(
29+
AddDynamicIfaceToStatus(
30+
newPod(podName, namespace, initialNetStatus...),
31+
newNetworkSelectionElementWithIface(networkName, ifaceName, namespace),
32+
newResponse(resultIPs...),
33+
),
34+
).To(Equal(expectedNetworkStatus))
35+
},
36+
Entry("initial empty pod", []nadv1.NetworkStatus{}, nil, `[{"name":"ns1/tenantnetwork","interface":"pepenet","mac":"02:03:04:05:06:07","dns":{}}]`),
37+
Entry("pod with a network present in the network status", []nadv1.NetworkStatus{
38+
{
39+
Name: "net1",
40+
Interface: "iface1",
41+
Mac: "00:00:00:20:10:00",
42+
}},
43+
nil,
44+
`[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}},{"name":"ns1/tenantnetwork","interface":"pepenet","mac":"02:03:04:05:06:07","dns":{}}]`),
45+
Entry("result with IPs", []nadv1.NetworkStatus{
46+
{
47+
Name: "net1",
48+
Interface: "iface1",
49+
Mac: "00:00:00:20:10:00",
50+
}},
51+
[]string{"10.10.10.10/24"},
52+
`[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}},{"name":"ns1/tenantnetwork","interface":"pepenet","ips":["10.10.10.10/24"],"mac":"02:03:04:05:06:07","dns":{}}]`))
53+
54+
DescribeTable("remove an interface to the current network status", func(initialNetStatus []nadv1.NetworkStatus, networkName, ifaceName, expectedNetworkStatus string) {
55+
Expect(
56+
DeleteDynamicIfaceFromStatus(
57+
newPod(podName, namespace, initialNetStatus...),
58+
networkName,
59+
ifaceName,
60+
),
61+
).To(Equal(expectedNetworkStatus))
62+
},
63+
Entry("when there aren't any existing interfaces", nil, "net1", "iface1", "[]"),
64+
Entry("when we remove all the currently existing interfaces", []nadv1.NetworkStatus{
65+
{
66+
Name: "net1",
67+
Interface: "iface1",
68+
Mac: "00:00:00:20:10:00",
69+
}}, "net1", "iface1", "[]"),
70+
Entry("when there is *not* a matching interface to remove", []nadv1.NetworkStatus{
71+
{
72+
Name: "net1",
73+
Interface: "iface1",
74+
Mac: "00:00:00:20:10:00",
75+
}}, "net2", "iface1", `[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`),
76+
Entry("when we remove one of the existing interfaces", []nadv1.NetworkStatus{
77+
{
78+
Name: "net1",
79+
Interface: "iface1",
80+
Mac: "00:00:00:20:10:00",
81+
},
82+
{
83+
Name: "net2",
84+
Interface: "iface2",
85+
Mac: "aa:bb:cc:20:10:00",
86+
},
87+
}, "net2", "iface2", `[{"name":"net1","interface":"iface1","mac":"00:00:00:20:10:00","dns":{}}]`))
88+
})
89+
90+
func newPod(podName string, namespace string, netStatus ...nadv1.NetworkStatus) *corev1.Pod {
91+
status, err := json.Marshal(netStatus)
92+
if err != nil {
93+
return nil
94+
}
95+
return &corev1.Pod{
96+
ObjectMeta: metav1.ObjectMeta{
97+
Name: podName,
98+
Namespace: namespace,
99+
Annotations: map[string]string{
100+
nadv1.NetworkStatusAnnot: string(status),
101+
},
102+
},
103+
}
104+
}
105+
106+
func newResponse(ips ...string) *api.Response {
107+
var ipConfs []*cni100.IPConfig
108+
for i := range ips {
109+
ip, ipNet, err := net.ParseCIDR(ips[i])
110+
if err != nil {
111+
112+
}
113+
ipNet.IP = ip
114+
ipConfs = append(ipConfs, &cni100.IPConfig{Address: *ipNet})
115+
}
116+
117+
ifaces := []*cni100.Interface{{
118+
Name: "pepenet",
119+
Mac: "02:03:04:05:06:07",
120+
Sandbox: "pepe",
121+
}}
122+
return &api.Response{
123+
Result: &cni100.Result{
124+
CNIVersion: "1.0.0",
125+
Interfaces: ifaces,
126+
IPs: ipConfs,
127+
}}
128+
}

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

+37
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"
@@ -274,6 +276,15 @@ func (pnc *PodNetworksController) addNetworks(dynamicAttachmentRequest *DynamicA
274276
}
275277
klog.Infof("response: %v", *response.Result)
276278

279+
newIfaceStatus, err := annotations.AddDynamicIfaceToStatus(pod, netToAdd, response)
280+
if err != nil {
281+
return fmt.Errorf("failed to compute the updated network status: %v", err)
282+
}
283+
284+
if err := pnc.updatePodNetworkStatus(pod, newIfaceStatus); err != nil {
285+
return err
286+
}
287+
277288
pnc.Eventf(pod, corev1.EventTypeNormal, "AddedInterface", addIfaceEventFormat(pod, netToAdd))
278289
}
279290

@@ -307,12 +318,38 @@ func (pnc *PodNetworksController) removeNetworks(dynamicAttachmentRequest *Dynam
307318
}
308319
klog.Infof("response: %v", *response)
309320

321+
newIfaceStatus, err := annotations.DeleteDynamicIfaceFromStatus(
322+
pod,
323+
namespacedName(netToRemove.Namespace, netToRemove.Name),
324+
netToRemove.InterfaceRequest,
325+
)
326+
if err != nil {
327+
return fmt.Errorf(
328+
"failed to compute the dynamic network attachments after deleting network: %s, iface: %s: %v",
329+
netToRemove.Name,
330+
netToRemove.InterfaceRequest,
331+
err,
332+
)
333+
}
334+
if err := pnc.updatePodNetworkStatus(pod, newIfaceStatus); err != nil {
335+
return err
336+
}
337+
310338
pnc.Eventf(pod, corev1.EventTypeNormal, "RemovedInterface", removeIfaceEventFormat(pod, netToRemove))
311339
}
312340

313341
return nil
314342
}
315343

344+
func (pnc *PodNetworksController) updatePodNetworkStatus(pod *corev1.Pod, newIfaceStatus string) error {
345+
pod.Annotations[nadv1.NetworkStatusAnnot] = newIfaceStatus
346+
347+
if _, err := pnc.k8sClientSet.CoreV1().Pods(pod.GetNamespace()).Update(context.Background(), pod, metav1.UpdateOptions{}); err != nil {
348+
return fmt.Errorf("failed to update pod's network-status annotations for %s: %v", pod.GetName(), err)
349+
}
350+
return nil
351+
}
352+
316353
func networkSelectionElements(podAnnotations map[string]string, podNamespace string) ([]*nadv1.NetworkSelectionElement, error) {
317354
podNetworks, ok := podAnnotations[nadv1.NetworkAttachmentAnnot]
318355
if !ok {

0 commit comments

Comments
 (0)