<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package tests

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/pcapgo"
	gclient "github.com/skydive-project/skydive/api/client"
	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/flow"
	g "github.com/skydive-project/skydive/gremlin"
	"github.com/skydive-project/skydive/tests/helper"
)

func TestSFlowProbeNode(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-spn", true},
			{"ovs-vsctl add-port br-spn spn-intf1 -- set interface spn-intf1 type=internal", true},
			{"ip address add 169.254.33.33/24 dev spn-intf1", true},
			{"ip link set spn-intf1 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "spn-intf1", "Type", "internal"),
			toIP:  "169.254.33.34",
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ovs-vsctl del-br br-spn", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-spn", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-spn", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows("NodeTID", node.Metadata()["TID"], "LayersPath", "Ethernet/ARP"))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return errors.New("Unable to find a flow with the expected NodeTID")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowNodeTIDOvsInternalNetNS(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-sntoin", true},
			{"ovs-vsctl add-port br-sntoin sntoin-intf1 -- set interface sntoin-intf1 type=internal", true},
			{"ip netns add sntoin-vm1", true},
			{"ip link set sntoin-intf1 netns sntoin-vm1", true},
			{"ip netns exec sntoin-vm1 ip address add 169.254.33.33/24 dev sntoin-intf1", true},
			{"ip netns exec sntoin-vm1 ip link set sntoin-intf1 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "sntoin-vm1").Out().Has("Name", "sntoin-intf1"),
			toIP:  "169.254.33.34",
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del sntoin-vm1", true},
			{"ovs-vsctl del-br br-sntoin", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-sntoin", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-sntoin", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows("NodeTID", node.Metadata()["TID"], "LayersPath", "Ethernet/ARP"))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return errors.New("Unable to find a flow with the expected NodeTID")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowTwoNodeTID(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-stnt1", true},
			{"ovs-vsctl add-port br-stnt1 stnt-intf1 -- set interface stnt-intf1 type=internal", true},
			{"ip netns add stnt-vm1", true},
			{"ip link set stnt-intf1 netns stnt-vm1", true},
			{"ip netns exec stnt-vm1 ip address add 169.254.33.33/24 dev stnt-intf1", true},
			{"ip netns exec stnt-vm1 ip link set stnt-intf1 up", true},

			{"ovs-vsctl add-br br-stnt2", true},
			{"ovs-vsctl add-port br-stnt2 stnt-intf2 -- set interface stnt-intf2 type=internal", true},
			{"ip netns add stnt-vm2", true},
			{"ip link set stnt-intf2 netns stnt-vm2", true},
			{"ip netns exec stnt-vm2 ip address add 169.254.33.34/24 dev stnt-intf2", true},
			{"ip netns exec stnt-vm2 ip link set stnt-intf2 up", true},

			// interfaces used to link br-stnt1 and br-stnt2 without a patch
			{"ovs-vsctl add-port br-stnt1 stnt-link1 -- set interface stnt-link1 type=internal", true},
			{"ip link set stnt-link1 up", true},
			{"ovs-vsctl add-port br-stnt2 stnt-link2 -- set interface stnt-link2 type=internal", true},
			{"ip link set stnt-link2 up", true},

			{"brctl addbr br-stnt-link", true},
			{"ip link set br-stnt-link up", true},
			{"brctl addif br-stnt-link stnt-link1", true},
			{"brctl addif br-stnt-link stnt-link2", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "stnt-vm1").Out().Has("Name", "stnt-intf1"),
			to:    g.G.V().Has("Name", "stnt-vm2").Out().Has("Name", "stnt-intf2"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del stnt-vm1", true},
			{"ip netns del stnt-vm2", true},
			{"ovs-vsctl del-br br-stnt1", true},
			{"ovs-vsctl del-br br-stnt2", true},
			{"sleep 1", true},
			{"ip link set br-stnt-link down", true},
			{"brctl delbr br-stnt-link", true},
		},

		captures: []TestCapture{
			{gremlin: `G.V().Has('Name', 'br-stnt1', 'Type', 'ovsbridge')`},
			{gremlin: `G.V().Has('Name', 'br-stnt2', 'Type', 'ovsbridge')`},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			flows, err := gh.GetFlows(prefix.V().Has("Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4"))
			if err != nil {
				return err
			}

			if len(flows) != 2 {
				return fmt.Errorf("Should have 2 flow entries one per NodeTID got: %d", len(flows))
			}

			node1, err := gh.GetNode(prefix.V().Has("Name", "br-stnt1", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			node2, err := gh.GetNode(prefix.V().Has("Name", "br-stnt2", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			tid1, _ := node1.GetFieldString("TID")
			tid2, _ := node2.GetFieldString("TID")

			if flows[0].NodeTID != tid1 && flows[0].NodeTID != tid2 {
				t.Errorf("Bad NodeTID for the first flow: %s", flows[0].NodeTID)
			}

			if flows[1].NodeTID != tid1 && flows[1].NodeTID != tid2 {
				t.Errorf("Bad NodeTID for the second flow: %s", flows[1].NodeTID)
			}

			if flows[0].TrackingID != flows[1].TrackingID {
				return fmt.Errorf("Both flows should have the same TrackingID: %v", flows)
			}

			if flows[0].UUID == flows[1].UUID {
				return fmt.Errorf("Both flows should have different UUID: %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestBPF(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"brctl addbr br-bpf", true},
			{"ip link set br-bpf up", true},
			{"ip netns add bpf-vm1", true},
			{"ip link add name bpf-vm1-eth0 type veth peer name eth0 netns bpf-vm1", true},
			{"ip link set bpf-vm1-eth0 up", true},
			{"ip netns exec bpf-vm1 ip link set eth0 up", true},
			{"ip netns exec bpf-vm1 ip address add 169.254.66.66/24 dev eth0", true},
			{"brctl addif br-bpf bpf-vm1-eth0", true},

			{"ip netns add bpf-vm2", true},
			{"ip link add name bpf-vm2-eth0 type veth peer name eth0 netns bpf-vm2", true},
			{"ip link set bpf-vm2-eth0 up", true},
			{"ip netns exec bpf-vm2 ip link set eth0 up", true},
			{"ip netns exec bpf-vm2 ip address add 169.254.66.67/24 dev eth0", true},
			{"brctl addif br-bpf bpf-vm2-eth0", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "bpf-vm1", "Type", "netns").Out().Has("Name", "eth0"),
			to:    g.G.V().Has("Name", "bpf-vm2", "Type", "netns").Out().Has("Name", "eth0"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip link set br-bpf down", true},
			{"brctl delbr br-bpf", true},
			{"ip link del bpf-vm1-eth0", true},
			{"ip link del bpf-vm2-eth0", true},
			{"ip netns del bpf-vm1", true},
			{"ip netns del bpf-vm2", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "bpf-vm1-eth0"), bpf: "icmp and host 169.254.66.67"},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			flows, err := c.gh.GetFlows(g.G.Context(c.time).V().Has("Name", "bpf-vm1-eth0").Flows())
			if err != nil {
				return err
			}

			if len(flows) != 1 {
				return fmt.Errorf("Should only get icmp packets, got : %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestPCAPProbe(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"brctl addbr br-pp", true},
			{"ip link set br-pp up", true},
			{"ip netns add pp-vm1", true},
			{"ip link add name pp-vm1-eth0 type veth peer name eth0 netns pp-vm1", true},
			{"ip link set pp-vm1-eth0 up", true},
			{"ip netns exec pp-vm1 ip link set eth0 up", true},
			{"ip netns exec pp-vm1 ip address add 169.254.66.66/24 dev eth0", true},
			{"brctl addif br-pp pp-vm1-eth0", true},

			{"ip netns add pp-vm2", true},
			{"ip link add name pp-vm2-eth0 type veth peer name eth0 netns pp-vm2", true},
			{"ip link set pp-vm2-eth0 up", true},
			{"ip netns exec pp-vm2 ip link set eth0 up", true},
			{"ip netns exec pp-vm2 ip address add 169.254.66.67/24 dev eth0", true},
			{"brctl addif br-pp pp-vm2-eth0", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "pp-vm1", "Type", "netns").Out().Has("Name", "eth0"),
			to:    g.G.V().Has("Name", "pp-vm2", "Type", "netns").Out().Has("Name", "eth0"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip link set br-pp down", true},
			{"brctl delbr br-pp", true},
			{"ip link del pp-vm1-eth0", true},
			{"ip link del pp-vm2-eth0", true},
			{"ip netns del pp-vm1", true},
			{"ip netns del pp-vm2", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-pp", "Type", "bridge"), kind: "pcap"},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-pp", "Type", "bridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows().Has("NodeTID", node.Metadata()["TID"]))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return fmt.Errorf("Unable to find a flow with the expected NodeTID: %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowSrcDstPath(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-ssdp", true},

			{"ovs-vsctl add-port br-ssdp ssdp-intf1 -- set interface ssdp-intf1 type=internal", true},
			{"ip netns add ssdp-vm1", true},
			{"ip link set ssdp-intf1 netns ssdp-vm1", true},
			{"ip netns exec ssdp-vm1 ip address add 169.254.33.33/24 dev ssdp-intf1", true},
			{"ip netns exec ssdp-vm1 ip link set ssdp-intf1 up", true},

			{"ovs-vsctl add-port br-ssdp ssdp-intf2 -- set interface ssdp-intf2 type=internal", true},
			{"ip netns add ssdp-vm2", true},
			{"ip link set ssdp-intf2 netns ssdp-vm2", true},
			{"ip netns exec ssdp-vm2 ip address add 169.254.33.34/24 dev ssdp-intf2", true},
			{"ip netns exec ssdp-vm2 ip link set ssdp-intf2 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "ssdp-intf1", "Type", "internal"),
			to:    g.G.V().Has("Name", "ssdp-intf2", "Type", "internal"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del ssdp-vm1", true},
			{"ip netns del ssdp-vm2", true},
			{"ovs-vsctl del-br br-ssdp", true},
		},

		captures: []TestCapture{
			{gremlin: `G.V().Has('Name', 'br-ssdp', 'Type', 'ovsbridge')`},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node1, err := gh.GetNode(prefix.V().Has("Name", "ssdp-intf1", "Type", "internal").HasKey("TID"))
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("ssdp-intf1 not found, %v", res)
			}

			node2, err := gh.GetNode(prefix.V().Has("Name", "ssdp-intf2", "Type", "internal").HasKey("TID"))
			if err != nil {
				var res interface{}
				gh.QueryObject(g.G, &res)
				return fmt.Errorf("ssdp-intf2 not found, %v", res)
			}

			srcNode, err := gh.GetNode(prefix.Flows().Has("Network.A", "169.254.33.33").In())
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("Source node not found, %v", res)
			}

			dstNode, err := gh.GetNode(prefix.Flows().Has("Network.A", "169.254.33.33").Out())
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("Destination node found, %v", res)
			}

			tid1, _ := node1.GetFieldString("TID")
			tid2, _ := srcNode.GetFieldString("TID")
			if tid1 != tid2 {
				return fmt.Errorf("Source Nodes not matching")
			}

			tid3, _ := node2.GetFieldString("TID")
			tid4, _ := dstNode.GetFieldString("TID")
			if tid3 != tid4 {
				return fmt.Errorf("Destination nodes not matching")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestFlowGremlin(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-fg", true},
			{"ovs-vsctl add-port br-fg fg-intf1 -- set interface fg-intf1 type=internal", true},
			{"ip address add 169.254.33.33/24 dev fg-intf1", true},
			{"ip link set fg-intf1 up", true},
		},

		injections: []TestInjection{{
			from: g.G.V().Has("Name", "fg-intf1", "Type", "internal"), toIP: "169.254.33.34", count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ovs-vsctl del-br br-fg", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-fg", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge"))
			if err != nil {
				return err
			}

			var count int64
			gh.QueryObject(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Count(), &count)
			if count != 1 {
				return fmt.Errorf("Should return 1, got: %d", count)
			}

			tid, _ := node.GetFieldString("TID")
			if tid == "" {
				return errors.New("Node TID not Found")
			}

			flows, _ := gh.GetFlows(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid))
			if len(flows) == 0 {
				return fmt.Errorf("Should return at least 1 flow, got: %v", flows)
			}

			flowsOpt, _ := gh.GetFlows(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid))
			if len(flowsOpt) != len(flows) {
				return fmt.Errorf("Should return the same number of flows that without optimisation, got: %v", flowsOpt)
			}

			nodes, _ := gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid).Out())
			if len(nodes) != 0 {
				return fmt.Errorf("Should return no destination node, got %d", len(nodes))
			}

			nodes, _ = gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid).Both().Dedup())
			if len(nodes) != 1 {
				return fmt.Errorf("Should return one node, got %d", len(nodes))
			}

			nodes, _ = gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid).In().Dedup())
			if len(nodes) != 1 {
				return fmt.Errorf("Should return one source node, got %d", len(nodes))
			}

			gh.QueryObject(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("NodeTID", tid).Count(), &count)
			if int(count) != len(flows) {
				return fmt.Errorf("Gremlin count doesn't correspond to the number of flows, got: %v, expected: %v", len(flows), count)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func queryFlowMetrics(gh *gclient.GremlinQueryHelper, bridge string, timeContext int64, pings int64) error {
	graphGremlin := g.G
	if timeContext != -1 {
		graphGremlin = graphGremlin.Context(timeContext)
	}

	ovsGremlin := graphGremlin.V().Has("Name", bridge, "Type", "ovsbridge")
	if _, err := gh.GetNode(ovsGremlin); err != nil {<commit_msg>TestFlowGremlin filter flow "LayersPath", "Ethernet/IPv4/ICMPv4"<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package tests

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/pcapgo"
	gclient "github.com/skydive-project/skydive/api/client"
	"github.com/skydive-project/skydive/api/types"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/flow"
	g "github.com/skydive-project/skydive/gremlin"
	"github.com/skydive-project/skydive/tests/helper"
)

func TestSFlowProbeNode(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-spn", true},
			{"ovs-vsctl add-port br-spn spn-intf1 -- set interface spn-intf1 type=internal", true},
			{"ip address add 169.254.33.33/24 dev spn-intf1", true},
			{"ip link set spn-intf1 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "spn-intf1", "Type", "internal"),
			toIP:  "169.254.33.34",
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ovs-vsctl del-br br-spn", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-spn", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-spn", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows("NodeTID", node.Metadata()["TID"], "LayersPath", "Ethernet/ARP"))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return errors.New("Unable to find a flow with the expected NodeTID")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowNodeTIDOvsInternalNetNS(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-sntoin", true},
			{"ovs-vsctl add-port br-sntoin sntoin-intf1 -- set interface sntoin-intf1 type=internal", true},
			{"ip netns add sntoin-vm1", true},
			{"ip link set sntoin-intf1 netns sntoin-vm1", true},
			{"ip netns exec sntoin-vm1 ip address add 169.254.33.33/24 dev sntoin-intf1", true},
			{"ip netns exec sntoin-vm1 ip link set sntoin-intf1 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "sntoin-vm1").Out().Has("Name", "sntoin-intf1"),
			toIP:  "169.254.33.34",
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del sntoin-vm1", true},
			{"ovs-vsctl del-br br-sntoin", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-sntoin", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-sntoin", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows("NodeTID", node.Metadata()["TID"], "LayersPath", "Ethernet/ARP"))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return errors.New("Unable to find a flow with the expected NodeTID")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowTwoNodeTID(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-stnt1", true},
			{"ovs-vsctl add-port br-stnt1 stnt-intf1 -- set interface stnt-intf1 type=internal", true},
			{"ip netns add stnt-vm1", true},
			{"ip link set stnt-intf1 netns stnt-vm1", true},
			{"ip netns exec stnt-vm1 ip address add 169.254.33.33/24 dev stnt-intf1", true},
			{"ip netns exec stnt-vm1 ip link set stnt-intf1 up", true},

			{"ovs-vsctl add-br br-stnt2", true},
			{"ovs-vsctl add-port br-stnt2 stnt-intf2 -- set interface stnt-intf2 type=internal", true},
			{"ip netns add stnt-vm2", true},
			{"ip link set stnt-intf2 netns stnt-vm2", true},
			{"ip netns exec stnt-vm2 ip address add 169.254.33.34/24 dev stnt-intf2", true},
			{"ip netns exec stnt-vm2 ip link set stnt-intf2 up", true},

			// interfaces used to link br-stnt1 and br-stnt2 without a patch
			{"ovs-vsctl add-port br-stnt1 stnt-link1 -- set interface stnt-link1 type=internal", true},
			{"ip link set stnt-link1 up", true},
			{"ovs-vsctl add-port br-stnt2 stnt-link2 -- set interface stnt-link2 type=internal", true},
			{"ip link set stnt-link2 up", true},

			{"brctl addbr br-stnt-link", true},
			{"ip link set br-stnt-link up", true},
			{"brctl addif br-stnt-link stnt-link1", true},
			{"brctl addif br-stnt-link stnt-link2", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "stnt-vm1").Out().Has("Name", "stnt-intf1"),
			to:    g.G.V().Has("Name", "stnt-vm2").Out().Has("Name", "stnt-intf2"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del stnt-vm1", true},
			{"ip netns del stnt-vm2", true},
			{"ovs-vsctl del-br br-stnt1", true},
			{"ovs-vsctl del-br br-stnt2", true},
			{"sleep 1", true},
			{"ip link set br-stnt-link down", true},
			{"brctl delbr br-stnt-link", true},
		},

		captures: []TestCapture{
			{gremlin: `G.V().Has('Name', 'br-stnt1', 'Type', 'ovsbridge')`},
			{gremlin: `G.V().Has('Name', 'br-stnt2', 'Type', 'ovsbridge')`},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			flows, err := gh.GetFlows(prefix.V().Has("Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4"))
			if err != nil {
				return err
			}

			if len(flows) != 2 {
				return fmt.Errorf("Should have 2 flow entries one per NodeTID got: %d", len(flows))
			}

			node1, err := gh.GetNode(prefix.V().Has("Name", "br-stnt1", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			node2, err := gh.GetNode(prefix.V().Has("Name", "br-stnt2", "Type", "ovsbridge").HasKey("TID"))
			if err != nil {
				return err
			}

			tid1, _ := node1.GetFieldString("TID")
			tid2, _ := node2.GetFieldString("TID")

			if flows[0].NodeTID != tid1 && flows[0].NodeTID != tid2 {
				t.Errorf("Bad NodeTID for the first flow: %s", flows[0].NodeTID)
			}

			if flows[1].NodeTID != tid1 && flows[1].NodeTID != tid2 {
				t.Errorf("Bad NodeTID for the second flow: %s", flows[1].NodeTID)
			}

			if flows[0].TrackingID != flows[1].TrackingID {
				return fmt.Errorf("Both flows should have the same TrackingID: %v", flows)
			}

			if flows[0].UUID == flows[1].UUID {
				return fmt.Errorf("Both flows should have different UUID: %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestBPF(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"brctl addbr br-bpf", true},
			{"ip link set br-bpf up", true},
			{"ip netns add bpf-vm1", true},
			{"ip link add name bpf-vm1-eth0 type veth peer name eth0 netns bpf-vm1", true},
			{"ip link set bpf-vm1-eth0 up", true},
			{"ip netns exec bpf-vm1 ip link set eth0 up", true},
			{"ip netns exec bpf-vm1 ip address add 169.254.66.66/24 dev eth0", true},
			{"brctl addif br-bpf bpf-vm1-eth0", true},

			{"ip netns add bpf-vm2", true},
			{"ip link add name bpf-vm2-eth0 type veth peer name eth0 netns bpf-vm2", true},
			{"ip link set bpf-vm2-eth0 up", true},
			{"ip netns exec bpf-vm2 ip link set eth0 up", true},
			{"ip netns exec bpf-vm2 ip address add 169.254.66.67/24 dev eth0", true},
			{"brctl addif br-bpf bpf-vm2-eth0", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "bpf-vm1", "Type", "netns").Out().Has("Name", "eth0"),
			to:    g.G.V().Has("Name", "bpf-vm2", "Type", "netns").Out().Has("Name", "eth0"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip link set br-bpf down", true},
			{"brctl delbr br-bpf", true},
			{"ip link del bpf-vm1-eth0", true},
			{"ip link del bpf-vm2-eth0", true},
			{"ip netns del bpf-vm1", true},
			{"ip netns del bpf-vm2", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "bpf-vm1-eth0"), bpf: "icmp and host 169.254.66.67"},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			flows, err := c.gh.GetFlows(g.G.Context(c.time).V().Has("Name", "bpf-vm1-eth0").Flows())
			if err != nil {
				return err
			}

			if len(flows) != 1 {
				return fmt.Errorf("Should only get icmp packets, got : %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestPCAPProbe(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"brctl addbr br-pp", true},
			{"ip link set br-pp up", true},
			{"ip netns add pp-vm1", true},
			{"ip link add name pp-vm1-eth0 type veth peer name eth0 netns pp-vm1", true},
			{"ip link set pp-vm1-eth0 up", true},
			{"ip netns exec pp-vm1 ip link set eth0 up", true},
			{"ip netns exec pp-vm1 ip address add 169.254.66.66/24 dev eth0", true},
			{"brctl addif br-pp pp-vm1-eth0", true},

			{"ip netns add pp-vm2", true},
			{"ip link add name pp-vm2-eth0 type veth peer name eth0 netns pp-vm2", true},
			{"ip link set pp-vm2-eth0 up", true},
			{"ip netns exec pp-vm2 ip link set eth0 up", true},
			{"ip netns exec pp-vm2 ip address add 169.254.66.67/24 dev eth0", true},
			{"brctl addif br-pp pp-vm2-eth0", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "pp-vm1", "Type", "netns").Out().Has("Name", "eth0"),
			to:    g.G.V().Has("Name", "pp-vm2", "Type", "netns").Out().Has("Name", "eth0"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip link set br-pp down", true},
			{"brctl delbr br-pp", true},
			{"ip link del pp-vm1-eth0", true},
			{"ip link del pp-vm2-eth0", true},
			{"ip netns del pp-vm1", true},
			{"ip netns del pp-vm2", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-pp", "Type", "bridge"), kind: "pcap"},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-pp", "Type", "bridge").HasKey("TID"))
			if err != nil {
				return err
			}

			flows, err := gh.GetFlows(prefix.Flows().Has("NodeTID", node.Metadata()["TID"]))
			if err != nil {
				return err
			}

			if len(flows) == 0 {
				return fmt.Errorf("Unable to find a flow with the expected NodeTID: %v", flows)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestSFlowSrcDstPath(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-ssdp", true},

			{"ovs-vsctl add-port br-ssdp ssdp-intf1 -- set interface ssdp-intf1 type=internal", true},
			{"ip netns add ssdp-vm1", true},
			{"ip link set ssdp-intf1 netns ssdp-vm1", true},
			{"ip netns exec ssdp-vm1 ip address add 169.254.33.33/24 dev ssdp-intf1", true},
			{"ip netns exec ssdp-vm1 ip link set ssdp-intf1 up", true},

			{"ovs-vsctl add-port br-ssdp ssdp-intf2 -- set interface ssdp-intf2 type=internal", true},
			{"ip netns add ssdp-vm2", true},
			{"ip link set ssdp-intf2 netns ssdp-vm2", true},
			{"ip netns exec ssdp-vm2 ip address add 169.254.33.34/24 dev ssdp-intf2", true},
			{"ip netns exec ssdp-vm2 ip link set ssdp-intf2 up", true},
		},

		injections: []TestInjection{{
			from:  g.G.V().Has("Name", "ssdp-intf1", "Type", "internal"),
			to:    g.G.V().Has("Name", "ssdp-intf2", "Type", "internal"),
			count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ip netns del ssdp-vm1", true},
			{"ip netns del ssdp-vm2", true},
			{"ovs-vsctl del-br br-ssdp", true},
		},

		captures: []TestCapture{
			{gremlin: `G.V().Has('Name', 'br-ssdp', 'Type', 'ovsbridge')`},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node1, err := gh.GetNode(prefix.V().Has("Name", "ssdp-intf1", "Type", "internal").HasKey("TID"))
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("ssdp-intf1 not found, %v", res)
			}

			node2, err := gh.GetNode(prefix.V().Has("Name", "ssdp-intf2", "Type", "internal").HasKey("TID"))
			if err != nil {
				var res interface{}
				gh.QueryObject(g.G, &res)
				return fmt.Errorf("ssdp-intf2 not found, %v", res)
			}

			srcNode, err := gh.GetNode(prefix.Flows().Has("Network.A", "169.254.33.33").In())
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("Source node not found, %v", res)
			}

			dstNode, err := gh.GetNode(prefix.Flows().Has("Network.A", "169.254.33.33").Out())
			if err != nil {
				var res interface{}
				gh.QueryObject("g", &res)
				return fmt.Errorf("Destination node found, %v", res)
			}

			tid1, _ := node1.GetFieldString("TID")
			tid2, _ := srcNode.GetFieldString("TID")
			if tid1 != tid2 {
				return fmt.Errorf("Source Nodes not matching")
			}

			tid3, _ := node2.GetFieldString("TID")
			tid4, _ := dstNode.GetFieldString("TID")
			if tid3 != tid4 {
				return fmt.Errorf("Destination nodes not matching")
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func TestFlowGremlin(t *testing.T) {
	test := &Test{
		setupCmds: []helper.Cmd{
			{"ovs-vsctl add-br br-fg", true},
			{"ovs-vsctl add-port br-fg fg-intf1 -- set interface fg-intf1 type=internal", true},
			{"ip address add 169.254.33.33/24 dev fg-intf1", true},
			{"ip link set fg-intf1 up", true},
		},

		injections: []TestInjection{{
			from: g.G.V().Has("Name", "fg-intf1", "Type", "internal"), toIP: "169.254.33.34", count: 5,
		}},

		tearDownCmds: []helper.Cmd{
			{"ovs-vsctl del-br br-fg", true},
		},

		captures: []TestCapture{
			{gremlin: g.G.V().Has("Name", "br-fg", "Type", "ovsbridge")},
		},

		checks: []CheckFunction{func(c *CheckContext) error {
			prefix := g.G.Context(c.time)
			gh := c.gh
			node, err := gh.GetNode(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge"))
			if err != nil {
				return err
			}

			var count int64
			gh.QueryObject(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Count(), &count)
			if count != 1 {
				return fmt.Errorf("Should return 1, got: %d", count)
			}

			tid, _ := node.GetFieldString("TID")
			if tid == "" {
				return errors.New("Node TID not Found")
			}

			flows, _ := gh.GetFlows(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid))
			if len(flows) == 0 {
				return fmt.Errorf("Should return at least 1 flow, got: %v", flows)
			}

			flowsOpt, _ := gh.GetFlows(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid))
			if len(flowsOpt) != len(flows) {
				return fmt.Errorf("Should return the same number of flows that without optimisation, got: %v", flowsOpt)
			}

			nodes, _ := gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid).Out())
			if len(nodes) != 0 {
				return fmt.Errorf("Should return no destination node, got %d", len(nodes))
			}

			nodes, _ = gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid).Both().Dedup())
			if len(nodes) != 1 {
				return fmt.Errorf("Should return one node, got %d", len(nodes))
			}

			nodes, _ = gh.GetNodes(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid).In().Dedup())
			if len(nodes) != 1 {
				return fmt.Errorf("Should return one source node, got %d", len(nodes))
			}

			gh.QueryObject(prefix.V().Has("Name", "br-fg", "Type", "ovsbridge").Flows().Has("LayersPath", "Ethernet/IPv4/ICMPv4", "NodeTID", tid).Count(), &count)
			if int(count) != len(flows) {
				return fmt.Errorf("Gremlin count doesn't correspond to the number of flows, got: %v, expected: %v", len(flows), count)
			}

			return nil
		}},
	}

	RunTest(t, test)
}

func queryFlowMetrics(gh *gclient.GremlinQueryHelper, bridge string, timeContext int64, pings int64) error {
	graphGremlin := g.G
	if timeContext != -1 {
		graphGremlin = graphGremlin.Context(timeContext)
	}

	ovsGremlin := graphGremlin.V().Has("Name", bridge, "Type", "ovsbridge")
	if _, err := gh.GetNode(ovsGremlin); err != nil {
{'fraction_non_alphanumeric': 0.19075005269338471, 'fraction_numerical': 0.026437023877630906, 'mean_word_length': 3.3740287106545503, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 34, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5352063', 'n_tokens_mistral': 14955, 'n_tokens_neox': 12932, 'n_words': 3270}