Skip to content

Commit 961bdbd

Browse files
authored
NETOBSERV-1904: Enrich using UDN info (#797)
* Enrich using UDN info * Add tests: - with MAC and UDN based enrichment at the same time - for UDN with plain IPs (instead of cidr), and with IPv6
1 parent 3981f85 commit 961bdbd

File tree

8 files changed

+299
-11
lines changed

8 files changed

+299
-11
lines changed

docs/api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ Following is the supported API format for network transformations:
268268
kubernetes: Kubernetes rule configuration
269269
ipField: entry IP input field
270270
interfacesField: entry Interfaces input field
271+
udnsField: entry UDNs input field
271272
macField: entry MAC input field
272273
output: entry output field
273274
assignee: value needs to assign to output field
@@ -294,7 +295,7 @@ Following is the supported API format for network transformations:
294295
configPath: path to kubeconfig file (optional)
295296
secondaryNetworks: configuration for secondary networks
296297
name: name of the secondary network, as mentioned in the annotation 'k8s.v1.cni.cncf.io/network-status'
297-
index: fields to use for indexing, must be any combination of 'mac', 'ip', 'interface'
298+
index: fields to use for indexing, must be any combination of 'mac', 'ip', 'interface', or 'udn'
298299
managedCNI: a list of CNI (network plugins) to manage, for detecting additional interfaces. Currently supported: ovn
299300
servicesFile: path to services file (optional, default: /etc/services)
300301
protocolsFile: path to protocols file (optional, default: /etc/protocols)

pkg/api/transform_network.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ type K8sReference struct {
8888
type K8sRule struct {
8989
IPField string `yaml:"ipField,omitempty" json:"ipField,omitempty" doc:"entry IP input field"`
9090
InterfacesField string `yaml:"interfacesField,omitempty" json:"interfacesField,omitempty" doc:"entry Interfaces input field"`
91+
UDNsField string `yaml:"udnsField,omitempty" json:"udnsField,omitempty" doc:"entry UDNs input field"`
9192
MACField string `yaml:"macField,omitempty" json:"macField,omitempty" doc:"entry MAC input field"`
9293
Output string `yaml:"output,omitempty" json:"output,omitempty" doc:"entry output field"`
9394
Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty" doc:"value needs to assign to output field"`
@@ -97,7 +98,7 @@ type K8sRule struct {
9798

9899
type SecondaryNetwork struct {
99100
Name string `yaml:"name,omitempty" json:"name,omitempty" doc:"name of the secondary network, as mentioned in the annotation 'k8s.v1.cni.cncf.io/network-status'"`
100-
Index map[string]any `yaml:"index,omitempty" json:"index,omitempty" doc:"fields to use for indexing, must be any combination of 'mac', 'ip', 'interface'"`
101+
Index map[string]any `yaml:"index,omitempty" json:"index,omitempty" doc:"fields to use for indexing, must be any combination of 'mac', 'ip', 'interface', or 'udn'"`
101102
}
102103

103104
type NetworkGenericRule struct {

pkg/pipeline/transform/kubernetes/cni/multus.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type SecondaryNetKey struct {
2626
Key string
2727
}
2828

29+
func (m *MultusHandler) Manages(indexKey string) bool {
30+
return indexKey == indexIP || indexKey == indexMAC || indexKey == indexInterface
31+
}
32+
2933
func (m *MultusHandler) BuildKeys(flow config.GenericMap, rule *api.K8sRule, secNets []api.SecondaryNetwork) []SecondaryNetKey {
3034
if len(secNets) == 0 {
3135
return nil
@@ -67,6 +71,9 @@ func (m *MultusHandler) buildSNKeys(flow config.GenericMap, rule *api.K8sRule, s
6771
return nil
6872
}
6973
}
74+
if mac == "" && ip == "" && len(interfaces) == 0 {
75+
return nil
76+
}
7077

7178
macIP := "~" + ip + "~" + mac
7279
if interfaces == nil {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package cni
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/netobserv/flowlogs-pipeline/pkg/api"
9+
"github.com/netobserv/flowlogs-pipeline/pkg/config"
10+
v1 "k8s.io/api/core/v1"
11+
)
12+
13+
const (
14+
ovnAnnotation = "k8s.ovn.org/pod-networks"
15+
)
16+
17+
type UDNHandler struct {
18+
}
19+
20+
func UDNKey(label, ip string) SecondaryNetKey {
21+
key := label + "~" + ip
22+
return SecondaryNetKey{NetworkName: label, Key: key}
23+
}
24+
25+
func (m *UDNHandler) Manages(indexKey string) bool {
26+
return indexKey == "udn"
27+
}
28+
29+
func (m *UDNHandler) BuildKeys(flow config.GenericMap, rule *api.K8sRule) []SecondaryNetKey {
30+
var keys []SecondaryNetKey
31+
32+
var ip string
33+
var udns []string
34+
var ok bool
35+
if len(rule.IPField) > 0 {
36+
ip, ok = flow.LookupString(rule.IPField)
37+
if !ok {
38+
return nil
39+
}
40+
}
41+
if len(rule.UDNsField) > 0 {
42+
v, ok := flow[rule.UDNsField]
43+
if !ok {
44+
return nil
45+
}
46+
udns, ok = v.([]string)
47+
if !ok || len(udns) == 0 {
48+
return nil
49+
}
50+
}
51+
52+
for _, udn := range udns {
53+
if udn != "" && udn != "default" {
54+
keys = append(keys, UDNKey(udn, ip))
55+
}
56+
}
57+
58+
return keys
59+
}
60+
61+
func (m *UDNHandler) GetPodUniqueKeys(pod *v1.Pod) ([]string, error) {
62+
// Example:
63+
// k8s.ovn.org/pod-networks: '{"default":{"ip_addresses":["10.128.2.20/23"],"mac_address":"0a:58:0a:80:02:14","routes":[{"dest":"10.128.0.0/14","nextHop":"10.128.2.1"},{"dest":"100.64.0.0/16","nextHop":"10.128.2.1"}],"ip_address":"10.128.2.20/23","role":"infrastructure-locked"},"mesh-arena/primary-udn":{"ip_addresses":["10.200.200.12/24"],"mac_address":"0a:58:0a:c8:c8:0c","gateway_ips":["10.200.200.1"],"routes":[{"dest":"172.30.0.0/16","nextHop":"10.200.200.1"},{"dest":"100.65.0.0/16","nextHop":"10.200.200.1"}],"ip_address":"10.200.200.12/24","gateway_ip":"10.200.200.1","tunnel_id":16,"role":"primary"}}'
64+
if statusAnnotationJSON, ok := pod.Annotations[ovnAnnotation]; ok {
65+
var annot map[string]map[string]any
66+
if err := json.Unmarshal([]byte(statusAnnotationJSON), &annot); err != nil {
67+
return nil, fmt.Errorf("failed to index from OVN annotation, cannot read annotation %s: %w", ovnAnnotation, err)
68+
}
69+
var keys []string
70+
for label, info := range annot {
71+
if label != "default" {
72+
if rawip, ok := info["ip_address"]; ok {
73+
if ip, ok := rawip.(string); ok {
74+
// IP has a CIDR prefix (bug??)
75+
parts := strings.SplitN(ip, "/", 2)
76+
if len(parts) > 0 {
77+
key := UDNKey(label, parts[0])
78+
keys = append(keys, key.Key)
79+
}
80+
}
81+
}
82+
}
83+
}
84+
return keys, nil
85+
}
86+
// Annotation not present => just ignore, no error
87+
return nil, nil
88+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cni
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
v1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
var (
13+
udnHandler = UDNHandler{}
14+
udnPod = v1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}
15+
)
16+
17+
func udnConfigAnnotation(ip string) string {
18+
return fmt.Sprintf(`{"default":{"ip_addresses":["10.128.2.20/23"],"mac_address":"0a:58:0a:80:02:14","routes":[{"dest":"10.128.0.0/14","nextHop":"10.128.2.1"},{"dest":"100.64.0.0/16","nextHop":"10.128.2.1"}],"ip_address":"10.128.2.20/23","role":"infrastructure-locked"},
19+
"mesh-arena/primary-udn":
20+
{"ip_addresses":["%s"],
21+
"mac_address":"0a:58:0a:c8:c8:0c",
22+
"gateway_ips":["10.200.200.1"],
23+
"routes":[{"dest":"172.30.0.0/16","nextHop":"10.200.200.1"},{"dest":"100.65.0.0/16","nextHop":"10.200.200.1"}],
24+
"ip_address":"%s",
25+
"gateway_ip":"10.200.200.1",
26+
"tunnel_id":16,
27+
"role":"primary"}}`, ip, ip)
28+
}
29+
30+
func TestExtractUDNStatusKeys(t *testing.T) {
31+
// Annotation not found => no error, no key
32+
keys, err := udnHandler.GetPodUniqueKeys(&udnPod)
33+
require.NoError(t, err)
34+
require.Empty(t, keys)
35+
36+
// Valid annotation => no error, valid key
37+
udnPod.Annotations = map[string]string{
38+
ovnAnnotation: udnConfigAnnotation("10.200.200.12"),
39+
}
40+
keys, err = udnHandler.GetPodUniqueKeys(&udnPod)
41+
require.NoError(t, err)
42+
require.Equal(t, []string{"mesh-arena/primary-udn~10.200.200.12"}, keys)
43+
44+
// Same check with a somewhat surprising CIDR found here as an IP, but it's really the IP part that should be used
45+
udnPod.Annotations = map[string]string{
46+
ovnAnnotation: udnConfigAnnotation("10.200.200.12/24"),
47+
}
48+
keys, err = udnHandler.GetPodUniqueKeys(&udnPod)
49+
require.NoError(t, err)
50+
require.Equal(t, []string{"mesh-arena/primary-udn~10.200.200.12"}, keys)
51+
52+
// Same with IPv6
53+
udnPod.Annotations = map[string]string{
54+
ovnAnnotation: udnConfigAnnotation("2001:0db8::1111"),
55+
}
56+
keys, err = udnHandler.GetPodUniqueKeys(&udnPod)
57+
require.NoError(t, err)
58+
require.Equal(t, []string{"mesh-arena/primary-udn~2001:0db8::1111"}, keys)
59+
60+
// Same with IPv6 as a CIDR
61+
udnPod.Annotations = map[string]string{
62+
ovnAnnotation: udnConfigAnnotation("2001:0db8::1111/24"),
63+
}
64+
keys, err = udnHandler.GetPodUniqueKeys(&udnPod)
65+
require.NoError(t, err)
66+
require.Equal(t, []string{"mesh-arena/primary-udn~2001:0db8::1111"}, keys)
67+
}

pkg/pipeline/transform/kubernetes/enrich_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,88 @@ func TestEnrichUsingMac(t *testing.T) {
472472
"SrcMAC": "AA:BB:CC:DD:EE:FF",
473473
}, entry)
474474
}
475+
476+
func TestEnrichUsingUDN(t *testing.T) {
477+
udnRules := api.NetworkTransformRules{
478+
{
479+
Type: api.NetworkAddKubernetes,
480+
Kubernetes: &api.K8sRule{
481+
IPField: "SrcAddr",
482+
MACField: "SrcMAC",
483+
UDNsField: "Udns",
484+
Output: "SrcK8s",
485+
AddZone: true,
486+
},
487+
},
488+
{
489+
Type: api.NetworkAddKubernetes,
490+
Kubernetes: &api.K8sRule{
491+
IPField: "DstAddr",
492+
MACField: "DstMAC",
493+
UDNsField: "Udns",
494+
Output: "DstK8s",
495+
AddZone: true,
496+
},
497+
},
498+
}
499+
customIndexes := map[string]*inf.Info{
500+
"~~AA:BB:CC:DD:EE:FF": {
501+
ObjectMeta: v1.ObjectMeta{
502+
Name: "pod-1",
503+
Namespace: "ns-1",
504+
},
505+
Type: "Pod",
506+
HostName: "host-1",
507+
HostIP: "100.0.0.1",
508+
NetworkName: "custom-network",
509+
},
510+
"ns-2/primary-udn~10.200.200.12": {
511+
ObjectMeta: v1.ObjectMeta{
512+
Name: "pod-2",
513+
Namespace: "ns-2",
514+
},
515+
Type: "Pod",
516+
HostName: "host-2",
517+
HostIP: "100.0.0.2",
518+
NetworkName: "ns-2/primary-udn",
519+
},
520+
}
521+
informers = inf.SetupStubs(ipInfo, customIndexes, nodes)
522+
523+
// MAC-indexed Pod 1 to UDN-indexed Pod 2
524+
entry := config.GenericMap{
525+
"SrcAddr": "8.8.8.8",
526+
"SrcMAC": "AA:BB:CC:DD:EE:FF", // pod-1
527+
"DstAddr": "10.200.200.12", // pod-2 (UDN)
528+
"DstMAC": "GG:HH:II:JJ:KK:LL", // unknown
529+
"Udns": []string{"", "default", "ns-2/primary-udn"},
530+
}
531+
for _, r := range udnRules {
532+
Enrich(entry, r.Kubernetes)
533+
}
534+
assert.Equal(t, config.GenericMap{
535+
"SrcAddr": "8.8.8.8",
536+
"SrcMAC": "AA:BB:CC:DD:EE:FF",
537+
"DstAddr": "10.200.200.12",
538+
"DstMAC": "GG:HH:II:JJ:KK:LL",
539+
"Udns": []string{"", "default", "ns-2/primary-udn"},
540+
"SrcK8s_HostIP": "100.0.0.1",
541+
"SrcK8s_HostName": "host-1",
542+
"SrcK8s_Name": "pod-1",
543+
"SrcK8s_Namespace": "ns-1",
544+
"SrcK8s_OwnerName": "",
545+
"SrcK8s_OwnerType": "",
546+
"SrcK8s_Type": "Pod",
547+
"SrcK8s_Zone": "us-east-1a",
548+
"SrcK8s_NetworkName": "custom-network",
549+
"DstK8s_HostIP": "100.0.0.2",
550+
"DstK8s_HostName": "host-2",
551+
"DstK8s_Name": "pod-2",
552+
"DstK8s_Namespace": "ns-2",
553+
"DstK8s_OwnerName": "",
554+
"DstK8s_OwnerType": "",
555+
"DstK8s_Type": "Pod",
556+
"DstK8s_Zone": "us-east-1b",
557+
"DstK8s_NetworkName": "ns-2/primary-udn",
558+
}, entry)
559+
}

pkg/pipeline/transform/kubernetes/informers/informers-mock.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ var (
1818
Name: "my-network",
1919
Index: map[string]any{"mac": nil},
2020
},
21+
{
22+
Name: "ovn-udn",
23+
Index: map[string]any{"udn": nil},
24+
},
2125
}
2226
)
2327

@@ -176,8 +180,8 @@ func (f *FakeInformers) InitFromConfig(_ api.NetworkTransformKubeConfig, _ *oper
176180
}
177181

178182
func (f *FakeInformers) GetInfo(keys []cni.SecondaryNetKey, ip string) (*Info, error) {
179-
if len(keys) > 0 {
180-
i := f.customKeysInfo[keys[0].Key]
183+
for _, key := range keys {
184+
i := f.customKeysInfo[key.Key]
181185
if i != nil {
182186
return i, nil
183187
}
@@ -191,8 +195,7 @@ func (f *FakeInformers) GetInfo(keys []cni.SecondaryNetKey, ip string) (*Info, e
191195
}
192196

193197
func (f *FakeInformers) BuildSecondaryNetworkKeys(flow config.GenericMap, rule *api.K8sRule) []cni.SecondaryNetKey {
194-
m := cni.MultusHandler{}
195-
return m.BuildKeys(flow, rule, secondaryNetConfig)
198+
return buildSecondaryNetworkKeys(flow, rule, secondaryNetConfig, true, true)
196199
}
197200

198201
func (f *FakeInformers) GetNodeInfo(n string) (*Info, error) {

0 commit comments

Comments
 (0)