Skip to content

Commit

Permalink
Add ParseDump function to allow using Fake.Dump() output as a test
Browse files Browse the repository at this point in the history
setup.

Signed-off-by: Nadia Pinaeva <npinaeva@redhat.com>
  • Loading branch information
npinaeva committed Jan 26, 2024
1 parent 634465f commit 2f8c43c
Show file tree
Hide file tree
Showing 3 changed files with 446 additions and 0 deletions.
75 changes: 75 additions & 0 deletions fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"reflect"
"regexp"
"sort"
"strings"
)
Expand Down Expand Up @@ -519,6 +520,80 @@ func (fake *Fake) Dump() string {
return buf.String()
}

var commentRegexp = regexp.MustCompile(`^(.*)comment "([^"]*)"( ;)?([^"]*)$`)

// splitComment splits line into a required pre-comment and optional trailing comment
// (which is enclosed in quotes but does not contain any quotes).
func splitComment(line string) (string, *string) {
//fmt.Printf("Split comment !%v!\n", line)
// We could perhaps do this more efficiently without using a regexp, but it would
// be more complicated...
match := commentRegexp.FindStringSubmatch(line)
if match != nil {
return strings.TrimSpace(match[1]) + match[4], &match[2]
}
return line, nil
}

// ParseDump can parse a dump for a given nft instance.
// It expects table name and the same nftables family in all rules based on a given fake.nftContext.
// The best way to verify that everything important was properly parsed is to
// compare given data with nft.Dump() output.
func (fake *Fake) ParseDump(data string) (err error) {
lines := strings.Split(data, "\n")
var i int
var line string
parsingDone := false
defer func() {
if err != nil && !parsingDone {
err = fmt.Errorf("line %v: %w", i+1, err)
}
}()
txn := fake.NewTransaction()
for i, line = range lines {
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' {
continue
}
words := strings.Split(line, " ")
if len(words) < 4 {
return fmt.Errorf("every command should have at least 4 words")
}
if words[0] != "add" {
return fmt.Errorf("only supports add operation, got %s", words[0])
}
if words[2] != string(fake.nftContext.family) {
return fmt.Errorf("wrong ip family %s, expected %s", words[2], string(fake.nftContext.family))
}
if words[3] != fake.nftContext.table {
return fmt.Errorf("wrong table name %s, expected %s", words[3], fake.nftContext.table)
}
var obj Object
switch words[1] {
case "table":
obj, err = parseTable(words)
case "chain":
obj, err = parseChain(words)
case "rule":
obj, err = parseRule(words)
case "map":
obj, err = parseMap(words)
case "set":
obj, err = parseSet(words)
case "element":
obj, err = parseElement(words)
default:
return fmt.Errorf("unknown object %s", words[1])
}
if err != nil {
return err
}
txn.Add(obj)
}
parsingDone = true
return fake.Run(context.TODO(), txn)
}

func sortKeys[K ~string, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for key := range m {
Expand Down
177 changes: 177 additions & 0 deletions fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,180 @@ func TestFakeAddInsertReplace(t *testing.T) {

assertRules(t, fake, "thirteenth", "sixth", "twelfth", "fifth", "seventh", "ninth", "eighth", "fourth", "third", "eleventh", "tenth")
}

func TestFakeParseDump(t *testing.T) {

tc1 := strings.TrimPrefix(dedent.Dedent(`
add table ip kube-proxy
add chain ip kube-proxy anotherchain
add chain ip kube-proxy chain { comment "foo" ; }
add map ip kube-proxy map1 { type ipv4_addr . inet_proto . inet_service ; }
add rule ip kube-proxy anotherchain ip saddr 1.2.3.4 drop comment "drop rule"
add rule ip kube-proxy anotherchain ip daddr 5.6.7.8 reject comment "reject rule"
add rule ip kube-proxy chain ip daddr 10.0.0.0/8 drop
add rule ip kube-proxy chain masquerade comment "comment"
add element ip kube-proxy map1 { 192.168.0.1 . tcp . 80 : drop }
add element ip kube-proxy map1 { 192.168.0.2 . tcp . 443 comment "with a comment" : goto anotherchain }
`), "\n")

tc2 := dedent.Dedent(`
add table ip kube-proxy { comment "rules for kube-proxy" ; }
add chain ip kube-proxy mark-for-masquerade
add chain ip kube-proxy masquerading
add chain ip kube-proxy services
add chain ip kube-proxy firewall-check
add chain ip kube-proxy endpoints-check
add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; }
add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; }
add chain ip kube-proxy filter-input { type filter hook input priority -110 ; }
add chain ip kube-proxy filter-output { type filter hook output priority -110 ; }
add chain ip kube-proxy nat-output { type nat hook output priority -100 ; }
add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; }
add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; }
add chain ip kube-proxy reject-chain { comment "helper for @no-endpoint-services / @no-endpoint-nodeports" ; }
add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80
add chain ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80
add chain ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80
add chain ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80
add chain ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80
add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80
add chain ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80
add chain ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80
add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80
add chain ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80
add chain ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80
add chain ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80
add chain ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80
add chain ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80
add chain ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80
add chain ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80
add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000
add rule ip kube-proxy masquerading mark and 0x4000 == 0 return
add rule ip kube-proxy masquerading mark set mark xor 0x4000
add rule ip kube-proxy masquerading masquerade fully-random
add rule ip kube-proxy filter-prerouting ct state new jump firewall-check
add rule ip kube-proxy filter-forward ct state new jump endpoints-check
add rule ip kube-proxy filter-input ct state new jump endpoints-check
add rule ip kube-proxy filter-output ct state new jump endpoints-check
add rule ip kube-proxy filter-output ct state new jump firewall-check
add rule ip kube-proxy nat-output jump services
add rule ip kube-proxy nat-postrouting jump masquerading
add rule ip kube-proxy nat-prerouting jump services
add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; }
add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips
add rule ip kube-proxy reject-chain reject
add map ip kube-proxy no-endpoint-services { type ipv4_addr . inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to services with no endpoints" ; }
add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; }
add rule ip kube-proxy endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services
add rule ip kube-proxy endpoints-check fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports
add map ip kube-proxy service-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "ClusterIP, ExternalIP and LoadBalancer IP traffic" ; }
add map ip kube-proxy service-nodeports { type inet_proto . inet_service : verdict ; comment "NodePort traffic" ; }
add rule ip kube-proxy services ip daddr . meta l4proto . th dport vmap @service-ips
add rule ip kube-proxy services fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @service-nodeports
# svc1
add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade
add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 }
add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade
add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
# svc2
add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade
add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 }
add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic"
add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic"
add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic"
add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade
add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80
add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy service-ips { 192.168.99.22 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy service-ips { 1.2.3.4 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy service-nodeports { tcp . 3001 : goto external-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy no-endpoint-nodeports { tcp . 3001 comment "ns2/svc2:p80" : drop }
add element ip kube-proxy no-endpoint-services { 1.2.3.4 . tcp . 80 comment "ns2/svc2:p80" : drop }
add element ip kube-proxy no-endpoint-services { 192.168.99.22 . tcp . 80 comment "ns2/svc2:p80" : drop }
# svc3
add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade
add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 }
add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 jump mark-for-masquerade
add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 goto service-4AT6LBPK-ns3/svc3/tcp/p80
add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade
add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 }
# svc4
add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade
add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 , 1 : goto endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 }
add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 jump mark-for-masquerade
add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 goto service-LAUZTJTB-ns4/svc4/tcp/p80
add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 ip saddr 10.180.0.5 jump mark-for-masquerade
add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 meta l4proto tcp dnat to 10.180.0.5:80
add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade
add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
add element ip kube-proxy service-ips { 192.168.99.33 . tcp . 80 : goto external-LAUZTJTB-ns4/svc4/tcp/p80 }
# svc5
add set ip kube-proxy affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { type ipv4_addr ; flags dynamic,timeout ; timeout 10800s ; }
add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip daddr 172.30.0.45 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade
add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80
add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 }
add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 jump mark-for-masquerade
add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 goto service-HVFWP5L3-ns5/svc5/tcp/p80
add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade
add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 update @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { ip saddr }
add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80
add rule ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr != { 203.0.113.0/25 } drop
add element ip kube-proxy service-ips { 172.30.0.45 . tcp . 80 : goto service-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy service-ips { 5.6.7.8 . tcp . 80 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy service-nodeports { tcp . 3002 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy firewall-ips { 5.6.7.8 . tcp . 80 comment "ns5/svc5:p80" : goto firewall-HVFWP5L3-ns5/svc5/tcp/p80 }
# svc6
add element ip kube-proxy no-endpoint-services { 172.30.0.46 . tcp . 80 comment "ns6/svc6:p80" : goto reject-chain }
`)

for _, rules := range []string{tc1, tc2} {
fake := NewFake(IPv4Family, "kube-proxy")
err := fake.ParseDump(rules)
if err != nil {
t.Fatalf("unexpected error from ParseDump: %v", err)
}

// Dump() had 1 empty line, add to rulesSlice to match
rulesSlice := []string{""}
for _, rule := range strings.Split(rules, "\n") {
if rule == "" || strings.HasPrefix(rule, "#") {
continue
}
rulesSlice = append(rulesSlice, rule)
}
sort.Sort(sort.StringSlice(rulesSlice))

Check failure on line 728 in fake_test.go

View workflow job for this annotation

GitHub Actions / verify (1.20.x, ubuntu-latest)

S1032: should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...)) (gosimple)

Check failure on line 728 in fake_test.go

View workflow job for this annotation

GitHub Actions / verify (1.21.x, ubuntu-latest)

S1032: should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...)) (gosimple)
dumpSlice := strings.Split(fake.Dump(), "\n")
sort.Sort(sort.StringSlice(dumpSlice))

Check failure on line 730 in fake_test.go

View workflow job for this annotation

GitHub Actions / verify (1.20.x, ubuntu-latest)

S1032: should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...)) (gosimple)

Check failure on line 730 in fake_test.go

View workflow job for this annotation

GitHub Actions / verify (1.21.x, ubuntu-latest)

S1032: should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...)) (gosimple)

diff := cmp.Diff(rulesSlice, dumpSlice)
if diff != "" {
t.Errorf("Dump doesn't match given rules:\n%s", diff)
}
}
}
Loading

0 comments on commit 2f8c43c

Please sign in to comment.