Skip to content

Commit 0ad6a39

Browse files
committed
controller: implement level driven controller
Signed-off-by: Miguel Duarte Barroso <[email protected]>
1 parent 90b2f03 commit 0ad6a39

6 files changed

+277
-186
lines changed

pkg/annotations/dynamic-network-status.go

+37-17
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func AddDynamicIfaceToStatus(currentPod *corev1.Pod, attachmentResults ...AttachmentResult) ([]nettypes.NetworkStatus, error) {
14-
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
14+
currentIfaceStatus, err := PodDynamicNetworkStatus(currentPod)
1515
if err != nil {
1616
return nil, err
1717
}
@@ -35,29 +35,28 @@ func AddDynamicIfaceToStatus(currentPod *corev1.Pod, attachmentResults ...Attach
3535
return currentIfaceStatus, nil
3636
}
3737

38-
func DeleteDynamicIfaceFromStatus(currentPod *corev1.Pod, networkSelectionElements ...*nettypes.NetworkSelectionElement) ([]nettypes.NetworkStatus, error) {
39-
currentIfaceStatus, err := podDynamicNetworkStatus(currentPod)
40-
if err != nil {
41-
return nil, err
38+
func DeleteDynamicIfaceFromStatus(currentPod *corev1.Pod, networkSelectionElements ...nettypes.NetworkSelectionElement) ([]nettypes.NetworkStatus, error) {
39+
indexedStatus := IndexNetworkStatus(currentPod)
40+
for _, networkSelectionElement := range networkSelectionElements {
41+
netStatusKey := fmt.Sprintf(
42+
"%s/%s",
43+
NamespacedName(networkSelectionElement.Namespace, networkSelectionElement.Name),
44+
networkSelectionElement.InterfaceRequest,
45+
)
46+
delete(indexedStatus, netStatusKey)
4247
}
4348

44-
var newIfaceStatus []nettypes.NetworkStatus
45-
for _, networkSelectionElement := range networkSelectionElements {
46-
netName := NamespacedName(networkSelectionElement.Namespace, networkSelectionElement.Name)
47-
newIfaceStatus = make([]nettypes.NetworkStatus, 0)
48-
for i := range currentIfaceStatus {
49-
if currentIfaceStatus[i].Name == netName && currentIfaceStatus[i].Interface == networkSelectionElement.InterfaceRequest {
50-
continue
51-
}
52-
newIfaceStatus = append(newIfaceStatus, currentIfaceStatus[i])
53-
}
49+
newIfaceStatus := make([]nettypes.NetworkStatus, 0)
50+
for networkStatusKey := range indexedStatus {
51+
newIfaceStatus = append(newIfaceStatus, indexedStatus[networkStatusKey])
5452
}
53+
5554
return newIfaceStatus, nil
5655
}
5756

58-
func podDynamicNetworkStatus(currentPod *corev1.Pod) ([]nettypes.NetworkStatus, error) {
57+
func PodDynamicNetworkStatus(currentPod *corev1.Pod) ([]nettypes.NetworkStatus, error) {
5958
var currentIfaceStatus []nettypes.NetworkStatus
60-
if currentIfaceStatusString, wasFound := currentPod.Annotations[nettypes.NetworkStatusAnnot]; wasFound {
59+
if currentIfaceStatusString, wasFound := currentPod.GetAnnotations()[nettypes.NetworkStatusAnnot]; wasFound {
6160
if err := json.Unmarshal([]byte(currentIfaceStatusString), &currentIfaceStatus); err != nil {
6261
return nil, fmt.Errorf("could not unmarshall the current dynamic annotations for pod %s: %v", podNameAndNs(currentPod), err)
6362
}
@@ -72,3 +71,24 @@ func podNameAndNs(currentPod *corev1.Pod) string {
7271
func NamespacedName(podNamespace string, podName string) string {
7372
return fmt.Sprintf("%s/%s", podNamespace, podName)
7473
}
74+
75+
func IndexNetworkStatus(pod *corev1.Pod) map[string]nettypes.NetworkStatus {
76+
currentPodNetworkStatus, err := PodDynamicNetworkStatus(pod)
77+
if err != nil {
78+
return map[string]nettypes.NetworkStatus{}
79+
}
80+
indexedNetworkStatus := map[string]nettypes.NetworkStatus{}
81+
for i := range currentPodNetworkStatus {
82+
if !currentPodNetworkStatus[i].Default {
83+
indexedNetworkStatus[networkStatusIndexKey(currentPodNetworkStatus[i])] = currentPodNetworkStatus[i]
84+
}
85+
}
86+
return indexedNetworkStatus
87+
}
88+
89+
func networkStatusIndexKey(networkStatus nettypes.NetworkStatus) string {
90+
return fmt.Sprintf(
91+
"%s/%s",
92+
networkStatus.Name,
93+
networkStatus.Interface)
94+
}

pkg/annotations/dynamic-network-status_test.go

+87-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package annotations
1+
package annotations_test
22

33
import (
44
"encoding/json"
@@ -12,10 +12,16 @@ import (
1212
corev1 "k8s.io/api/core/v1"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414

15+
"github.com/k8snetworkplumbingwg/multus-dynamic-networks-controller/pkg/annotations"
1516
nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
1617
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/server/api"
1718
)
1819

20+
type attachmentInfo struct {
21+
ifaceName string
22+
networkName string
23+
}
24+
1925
var _ = Describe("NetworkStatusFromResponse", func() {
2026
const (
2127
ifaceName = "ens32"
@@ -28,9 +34,9 @@ var _ = Describe("NetworkStatusFromResponse", func() {
2834

2935
DescribeTable("add dynamic interface to network status", func(initialNetStatus []nadv1.NetworkStatus, resultIPs []string, expectedNetworkStatus []nadv1.NetworkStatus) {
3036
Expect(
31-
AddDynamicIfaceToStatus(
37+
annotations.AddDynamicIfaceToStatus(
3238
newPod(podName, namespace, initialNetStatus...),
33-
*NewAttachmentResult(
39+
*annotations.NewAttachmentResult(
3440
newNetworkSelectionElementWithIface(networkName, ifaceName, namespace),
3541
newResponse(ifaceToAdd, macAddr, resultIPs...),
3642
),
@@ -39,7 +45,7 @@ var _ = Describe("NetworkStatusFromResponse", func() {
3945
},
4046
Entry("initial empty pod", []nadv1.NetworkStatus{}, nil, []nadv1.NetworkStatus{
4147
{
42-
Name: NamespacedName(namespace, networkName),
48+
Name: annotations.NamespacedName(namespace, networkName),
4349
Interface: ifaceToAdd,
4450
Mac: macAddr,
4551
DNS: nadv1.DNS{
@@ -63,7 +69,7 @@ var _ = Describe("NetworkStatusFromResponse", func() {
6369
Mac: "00:00:00:20:10:00",
6470
},
6571
{
66-
Name: NamespacedName(namespace, networkName),
72+
Name: annotations.NamespacedName(namespace, networkName),
6773
Interface: ifaceToAdd,
6874
Mac: macAddr,
6975
DNS: nadv1.DNS{
@@ -88,7 +94,7 @@ var _ = Describe("NetworkStatusFromResponse", func() {
8894
Mac: "00:00:00:20:10:00",
8995
},
9096
{
91-
Name: NamespacedName(namespace, networkName),
97+
Name: annotations.NamespacedName(namespace, networkName),
9298
Interface: ifaceToAdd,
9399
Mac: macAddr,
94100
IPs: []string{"10.10.10.10"},
@@ -101,59 +107,112 @@ var _ = Describe("NetworkStatusFromResponse", func() {
101107
}},
102108
))
103109

104-
DescribeTable("remove an interface to the current network status", func(initialNetStatus []nadv1.NetworkStatus, networkName string, ifaceToRemove string, expectedNetworkStatus []nadv1.NetworkStatus) {
110+
DescribeTable("remove an interface to the current network status", func(initialNetStatus []nadv1.NetworkStatus, expectedNetworkStatus []nadv1.NetworkStatus, ifacesToRemove ...attachmentInfo) {
111+
var netsToRemove []nadv1.NetworkSelectionElement
112+
for _, ifaceToRemove := range ifacesToRemove {
113+
netsToRemove = append(
114+
netsToRemove,
115+
*newNetworkSelectionElementWithIface(ifaceToRemove.networkName, ifaceToRemove.ifaceName, namespace),
116+
)
117+
}
105118
Expect(
106-
DeleteDynamicIfaceFromStatus(
107-
newPod(podName, namespace, initialNetStatus...),
108-
newNetworkSelectionElementWithIface(networkName, ifaceToRemove, namespace),
109-
),
119+
annotations.DeleteDynamicIfaceFromStatus(newPod(podName, namespace, initialNetStatus...), netsToRemove...),
110120
).To(Equal(expectedNetworkStatus))
111121
},
112-
Entry("when there aren't any existing interfaces", nil, "net1", "iface1", []nadv1.NetworkStatus{}),
113-
Entry("when we remove all the currently existing interfaces", []nadv1.NetworkStatus{
114-
{
115-
Name: NamespacedName(namespace, networkName),
116-
Interface: "iface1",
117-
Mac: "00:00:00:20:10:00",
118-
}}, networkName, "iface1", []nadv1.NetworkStatus{}),
122+
Entry("when there aren't any existing interfaces", nil, []nadv1.NetworkStatus{}, attachmentInfo{
123+
ifaceName: "iface1",
124+
networkName: "net1",
125+
}),
126+
Entry(
127+
"when we remove all the currently existing interfaces",
128+
[]nadv1.NetworkStatus{
129+
{
130+
Name: annotations.NamespacedName(namespace, networkName),
131+
Interface: "iface1",
132+
Mac: "00:00:00:20:10:00",
133+
}},
134+
[]nadv1.NetworkStatus{},
135+
attachmentInfo{
136+
ifaceName: "iface1",
137+
networkName: networkName,
138+
},
139+
),
119140
Entry("when there is *not* a matching interface to remove", []nadv1.NetworkStatus{
120141
{
121-
Name: NamespacedName(namespace, networkName),
142+
Name: annotations.NamespacedName(namespace, networkName),
122143
Interface: "iface1",
123144
Mac: "00:00:00:20:10:00",
124145
}},
125-
"net2",
126-
"iface1",
127146
[]nadv1.NetworkStatus{
128147
{
129-
Name: NamespacedName(namespace, networkName),
148+
Name: annotations.NamespacedName(namespace, networkName),
130149
Interface: "iface1",
131150
Mac: "00:00:00:20:10:00",
132151
},
133152
},
153+
attachmentInfo{
154+
ifaceName: "iface1",
155+
networkName: "net2",
156+
},
134157
),
135158
Entry("when we remove one of the existing interfaces", []nadv1.NetworkStatus{
136159
{
137-
Name: NamespacedName(namespace, networkName),
160+
Name: annotations.NamespacedName(namespace, networkName),
138161
Interface: "iface1",
139162
Mac: "00:00:00:20:10:00",
140163
},
141164
{
142-
Name: NamespacedName(namespace, "net2"),
165+
Name: annotations.NamespacedName(namespace, "net2"),
143166
Interface: "iface2",
144167
Mac: "aa:bb:cc:20:10:00",
145168
},
146169
},
147-
"net2",
148-
"iface2",
149170
[]nadv1.NetworkStatus{
150171
{
151-
Name: NamespacedName(namespace, networkName),
172+
Name: annotations.NamespacedName(namespace, networkName),
152173
Interface: "iface1",
153174
Mac: "00:00:00:20:10:00",
154175
},
155176
},
156-
))
177+
attachmentInfo{
178+
ifaceName: "iface2",
179+
networkName: "net2",
180+
},
181+
),
182+
Entry(
183+
"when we remove multiple interfaces at once",
184+
[]nadv1.NetworkStatus{
185+
{
186+
Name: annotations.NamespacedName(namespace, networkName),
187+
Interface: "iface1",
188+
Mac: "00:00:00:20:10:00",
189+
},
190+
{
191+
Name: annotations.NamespacedName(namespace, "net2"),
192+
Interface: "iface2",
193+
Mac: "aa:bb:cc:20:10:00",
194+
},
195+
{
196+
Name: annotations.NamespacedName(namespace, "net3"),
197+
Interface: "iface3",
198+
Mac: "aa:bb:cc:11:11:11",
199+
},
200+
},
201+
[]nadv1.NetworkStatus{
202+
{
203+
Name: annotations.NamespacedName(namespace, "net3"),
204+
Interface: "iface3",
205+
Mac: "aa:bb:cc:11:11:11",
206+
},
207+
},
208+
attachmentInfo{
209+
ifaceName: "iface1",
210+
networkName: networkName,
211+
},
212+
attachmentInfo{
213+
ifaceName: "iface2",
214+
networkName: "net2",
215+
}))
157216
})
158217

159218
func newPod(podName string, namespace string, netStatus ...nadv1.NetworkStatus) *corev1.Pod {

pkg/annotations/network-selection-elements.go

+48
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"regexp"
2424
"strings"
2525

26+
corev1 "k8s.io/api/core/v1"
2627
"k8s.io/klog/v2"
2728

2829
nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
@@ -134,3 +135,50 @@ func parsePodNetworkObjectName(podnetwork string) (string, string, string, error
134135
klog.V(5).Infof("parsePodNetworkObjectName: parsed: %s, %s, %s", netNsName, networkName, netIfName)
135136
return netNsName, networkName, netIfName, nil
136137
}
138+
139+
func IndexPodNetworkSelectionElements(pod *corev1.Pod) map[string]nadv1.NetworkSelectionElement {
140+
currentPodNetworkSelectionElements, err := networkSelectionElements(pod.GetAnnotations(), pod.GetNamespace())
141+
if err != nil {
142+
klog.Errorf("could not read pod's network selection elements: %v", *pod)
143+
return map[string]nadv1.NetworkSelectionElement{}
144+
}
145+
indexedNetworkSelectionElements := make(map[string]nadv1.NetworkSelectionElement)
146+
for k := range currentPodNetworkSelectionElements {
147+
netSelectionElement := currentPodNetworkSelectionElements[k]
148+
indexedNetworkSelectionElements[NetworkSelectionElementIndexKey(netSelectionElement)] = netSelectionElement
149+
}
150+
return indexedNetworkSelectionElements
151+
}
152+
153+
func networkSelectionElements(podAnnotations map[string]string, podNamespace string) ([]nadv1.NetworkSelectionElement, error) {
154+
podNetworks, ok := podAnnotations[nadv1.NetworkAttachmentAnnot]
155+
if !ok || podNetworks == "" {
156+
return []nadv1.NetworkSelectionElement{}, nil
157+
}
158+
podNetworkSelectionElements, err := ParsePodNetworkAnnotations(podNetworks, podNamespace)
159+
if err != nil {
160+
klog.Errorf("failed to extract the network selection elements: %v", err)
161+
return nil, err
162+
}
163+
164+
var currentPodNetworkSelectionElements []nadv1.NetworkSelectionElement
165+
for i := range podNetworkSelectionElements {
166+
currentPodNetworkSelectionElements = append(currentPodNetworkSelectionElements, *podNetworkSelectionElements[i])
167+
}
168+
return currentPodNetworkSelectionElements, nil
169+
}
170+
171+
func NetworkSelectionElementIndexKey(netSelectionElement nadv1.NetworkSelectionElement) string {
172+
if netSelectionElement.InterfaceRequest != "" {
173+
return fmt.Sprintf(
174+
"%s/%s/%s",
175+
netSelectionElement.Namespace,
176+
netSelectionElement.Name,
177+
netSelectionElement.InterfaceRequest)
178+
}
179+
180+
return fmt.Sprintf(
181+
"%s/%s",
182+
netSelectionElement.Namespace,
183+
netSelectionElement.Name)
184+
}

pkg/annotations/network-selection-elements_test.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package annotations
1+
package annotations_test
22

33
import (
44
"strings"
@@ -7,6 +7,7 @@ import (
77
. "github.com/onsi/ginkgo/v2"
88
. "github.com/onsi/gomega"
99

10+
"github.com/k8snetworkplumbingwg/multus-dynamic-networks-controller/pkg/annotations"
1011
v1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
1112
)
1213

@@ -19,17 +20,17 @@ var _ = Describe("Parsing annotations", func() {
1920
const namespace = "ns1"
2021

2122
It("nil input", func() {
22-
_, err := ParsePodNetworkAnnotations("", namespace)
23+
_, err := annotations.ParsePodNetworkAnnotations("", namespace)
2324
Expect(err).To(MatchError("parsePodNetworkAnnotation: pod annotation does not have \"network\" as key"))
2425
})
2526

2627
It("empty list input", func() {
27-
Expect(ParsePodNetworkAnnotations("[]", namespace)).To(BeEmpty())
28+
Expect(annotations.ParsePodNetworkAnnotations("[]", namespace)).To(BeEmpty())
2829
})
2930

3031
It("single network name", func() {
3132
const networkName = "net1"
32-
Expect(ParsePodNetworkAnnotations(networkName, namespace)).To(ConsistOf(newNetworkSelectionElement(networkName, namespace)))
33+
Expect(annotations.ParsePodNetworkAnnotations(networkName, namespace)).To(ConsistOf(newNetworkSelectionElement(networkName, namespace)))
3334
})
3435

3536
It("comma separated list of network names", func() {
@@ -38,7 +39,7 @@ var _ = Describe("Parsing annotations", func() {
3839
secondNetworkName = "net321"
3940
)
4041
Expect(
41-
ParsePodNetworkAnnotations(networkSelectionElements(networkName, secondNetworkName), namespace),
42+
annotations.ParsePodNetworkAnnotations(networkSelectionElements(networkName, secondNetworkName), namespace),
4243
).To(
4344
ConsistOf(
4445
newNetworkSelectionElement(networkName, namespace),
@@ -51,7 +52,7 @@ var _ = Describe("Parsing annotations", func() {
5152
secondNetworkAndInterfaceNamingPair = "net321@eth2"
5253
)
5354
Expect(
54-
ParsePodNetworkAnnotations(
55+
annotations.ParsePodNetworkAnnotations(
5556
networkSelectionElements(
5657
networkAndInterfaceNamingPair,
5758
secondNetworkAndInterfaceNamingPair),
@@ -65,7 +66,7 @@ var _ = Describe("Parsing annotations", func() {
6566
It("network selection element specified in JSON", func() {
6667
const networkSelectionElementsString = "[\n { \"name\" : \"macvlan-conf-1\" },\n { \"name\" : \"macvlan-conf-2\", \"interface\": \"ens4\" }\n ]"
6768
Expect(
68-
ParsePodNetworkAnnotations(networkSelectionElementsString, namespace),
69+
annotations.ParsePodNetworkAnnotations(networkSelectionElementsString, namespace),
6970
).To(
7071
ConsistOf(
7172
newNetworkSelectionElement("macvlan-conf-1", namespace),

0 commit comments

Comments
 (0)