func TestGraphPathTraversal(t *testing.T) { g := newGraph(t) n1 := g.NewNode(graph.GenID(), graph.Metadata{"Type": "host", "Name": "localhost"}) n2 := g.NewNode(graph.GenID(), graph.Metadata{"Name": "N2", "Type": "T2"}) n3 := g.NewNode(graph.GenID(), graph.Metadata{"Name": "N3", "Type": "T3"}) g.Link(n1, n2, graph.Metadata{"RelationType": "ownership"}) g.Link(n2, n3, graph.Metadata{"RelationType": "ownership"}) query := `G.V().Has("Name", "N3").GraphPath()` tp := traversal.NewGremlinTraversalParser(strings.NewReader(query), g) tp.AddTraversalExtension(NewTopologyTraversalExtension()) ts, err := tp.Parse() if err != nil { t.Fatal(err.Error()) } res, err := ts.Exec() if err != nil { t.Fatal(err.Error()) } if len(res.Values()) != 1 || res.Values()[0].(string) != "localhost[Type=host]/N2[Type=T2]/N3[Type=T3]" { t.Fatalf("Should return 1 path, returned: %v", res.Values()) } }
func newTransversalGraph(t *testing.T) *graph.Graph { g := newGraph(t) n1 := g.NewNode(graph.GenID(), graph.Metadata{"Value": 1, "Type": "intf", "Bytes": 1024}) n2 := g.NewNode(graph.GenID(), graph.Metadata{"Value": 2, "Type": "intf", "Bytes": 2024}) n3 := g.NewNode(graph.GenID(), graph.Metadata{"Value": 3}) n4 := g.NewNode(graph.GenID(), graph.Metadata{"Value": 4, "Name": "Node4", "Bytes": 4024}) g.Link(n1, n2, graph.Metadata{"Direction": "Left"}) g.Link(n2, n3, graph.Metadata{"Direction": "Left"}) g.Link(n3, n4) g.Link(n1, n4) g.Link(n1, n3, graph.Metadata{"Mode": "Direct"}) return g }
func TestMarshal(t *testing.T) { g := newGraph(t) n1 := g.NewNode(graph.GenID(), graph.Metadata{"Name": "N1", "Type": "T1"}) n2 := g.NewNode(graph.GenID(), graph.Metadata{"Name": "N2", "Type": "T2"}) n3 := g.NewNode(graph.GenID(), graph.Metadata{"Name": "N3", "Type": "T3"}) g.Link(n1, n2) g.Link(n2, n3) r := g.LookupShortestPath(n3, graph.Metadata{"Name": "N1"}) if len(r) == 0 { t.Errorf("Wrong nodes returned: %v", r) } path := NodePath(r).Marshal() if path != "N1[Type=T1]/N2[Type=T2]/N3[Type=T3]" { t.Errorf("Wrong path returned: %s", path) } }
func (u *NetLinkProbe) addOvsLinkToTopology(link netlink.Link, m graph.Metadata) *graph.Node { name := link.Attrs().Name intf := u.Graph.LookupFirstNode(graph.Metadata{"Name": name, "Driver": "openvswitch"}) if intf == nil { intf = u.Graph.NewNode(graph.GenID(), m) } if !u.Graph.AreLinked(u.Root, intf) { u.Graph.Link(u.Root, intf, graph.Metadata{"RelationType": "ownership"}) } return intf }
func (u *NetNSProbe) Register(path string, extraMetadata graph.Metadata) *graph.Node { ns, ok := u.pathToNetNS[path] if !ok { var s syscall.Stat_t fd, err := syscall.Open(path, syscall.O_RDONLY, 0) if err != nil { logging.GetLogger().Errorf("Error registering namespace %s: %s", path, err.Error()) return nil } defer syscall.Close(fd) if err := syscall.Fstat(fd, &s); err != nil { logging.GetLogger().Errorf("Error reading namespace %s: %s", path, err.Error()) return nil } ns = &NetNs{path: path, dev: s.Dev, ino: s.Ino} u.pathToNetNS[path] = ns } u.Lock() defer u.Unlock() nsString := ns.String() probe, ok := u.nsnlProbes[nsString] if ok { probe.useCount++ logging.GetLogger().Debugf("Increasing counter for namespace %s to %d", nsString, probe.useCount) return probe.Root } u.Graph.Lock() defer u.Graph.Unlock() logging.GetLogger().Debugf("Network Namespace added: %s", nsString) metadata := graph.Metadata{"Name": getNetNSName(path), "Type": "netns", "Path": path} if extraMetadata != nil { for k, v := range extraMetadata { metadata[k] = v } } n := u.Graph.NewNode(graph.GenID(), metadata) u.Graph.Link(u.Root, n, graph.Metadata{"RelationType": "ownership"}) nu := NewNetNsNetLinkTopoUpdater(u.Graph, n) go nu.Start(ns) u.nsnlProbes[nsString] = nu return n }
func (probe *DockerProbe) registerContainer(id string) { probe.Lock() defer probe.Unlock() if _, ok := probe.containerMap[id]; ok { return } info, err := probe.client.ContainerInspect(context.Background(), id) if err != nil { logging.GetLogger().Errorf("Failed to inspect Docker container %s: %s", id, err.Error()) return } nsHandle, err := netns.GetFromPid(info.State.Pid) if err != nil { return } defer nsHandle.Close() namespace := probe.containerNamespace(info.State.Pid) logging.GetLogger().Debugf("Register docker container %s and PID %d", info.ID, info.State.Pid) var n *graph.Node if probe.hostNs.Equal(nsHandle) { // The container is in net=host mode n = probe.Root } else { n = probe.Register(namespace, graph.Metadata{"Name": info.Name[1:], "Manager": "docker"}) } probe.Graph.Lock() metadata := graph.Metadata{ "Type": "container", "Name": info.Name[1:], "Docker/ContainerID": info.ID, "Docker/ContainerName": info.Name, "Docker/ContainerPID": info.State.Pid, } containerNode := probe.Graph.NewNode(graph.GenID(), metadata) probe.Graph.Link(n, containerNode, graph.Metadata{"RelationType": "membership"}) probe.Graph.Unlock() probe.containerMap[info.ID] = ContainerInfo{ Pid: info.State.Pid, Node: containerNode, } }
func (u *NetLinkProbe) addGenericLinkToTopology(link netlink.Link, m graph.Metadata) *graph.Node { name := link.Attrs().Name index := int64(link.Attrs().Index) var intf *graph.Node intf = u.Graph.LookupFirstChild(u.Root, graph.Metadata{ "IfIndex": index, }) // could be a member of ovs intfs := u.Graph.GetNodes(graph.Metadata{ "Name": name, "IfIndex": index, }) for _, i := range intfs { if _, ok := i.Metadata()["UUID"]; ok { intf = i break } } if intf == nil { intf = u.Graph.NewNode(graph.GenID(), m) } if intf == nil { return nil } if !u.Graph.AreLinked(u.Root, intf) { u.Graph.Link(u.Root, intf, graph.Metadata{"RelationType": "ownership"}) } // ignore ovs-system interface as it doesn't make any sense according to // the following thread: // http://openvswitch.org/pipermail/discuss/2013-October/011657.html if name == "ovs-system" { return intf } u.handleIntfIsChild(intf, link) u.handleIntfIsVeth(intf, link) u.handleIntfIsBond(intf, link) return intf }
func (o *OvsdbProbe) OnOvsBridgeAdd(monitor *ovsdb.OvsMonitor, uuid string, row *libovsdb.RowUpdate) { o.Lock() defer o.Unlock() name := row.New.Fields["name"].(string) o.Graph.Lock() defer o.Graph.Unlock() bridge := o.Graph.LookupFirstNode(graph.Metadata{"UUID": uuid}) if bridge == nil { bridge = o.Graph.NewNode(graph.GenID(), graph.Metadata{"Name": name, "UUID": uuid, "Type": "ovsbridge"}) o.Graph.Link(o.Root, bridge, graph.Metadata{"RelationType": "ownership"}) } switch row.New.Fields["ports"].(type) { case libovsdb.OvsSet: set := row.New.Fields["ports"].(libovsdb.OvsSet) for _, i := range set.GoSet { u := i.(libovsdb.UUID).GoUUID port, ok := o.uuidToPort[u] if ok && !o.Graph.AreLinked(bridge, port) { o.Graph.Link(bridge, port, graph.Metadata{"RelationType": "layer2"}) } else { /* will be filled later when the port update for this port will be triggered */ o.portBridgeQueue[u] = bridge } } case libovsdb.UUID: u := row.New.Fields["ports"].(libovsdb.UUID).GoUUID port, ok := o.uuidToPort[u] if ok && !o.Graph.AreLinked(bridge, port) { o.Graph.Link(bridge, port, graph.Metadata{"RelationType": "layer2"}) } else { /* will be filled later when the port update for this port will be triggered */ o.portBridgeQueue[u] = bridge } } }
func (u *NetLinkProbe) addBridgeLinkToTopology(link netlink.Link, m graph.Metadata) *graph.Node { name := link.Attrs().Name index := int64(link.Attrs().Index) intf := u.Graph.LookupFirstChild(u.Root, graph.Metadata{ "Name": name, "IfIndex": index, }) if intf == nil { intf = u.Graph.NewNode(graph.GenID(), m) } if !u.Graph.AreLinked(u.Root, intf) { u.Graph.Link(u.Root, intf, graph.Metadata{"RelationType": "ownership"}) } u.linkPendingChildren(intf, index) return intf }
func TestRegexPredicate(t *testing.T) { g := newGraph(t) g.NewNode(graph.GenID(), graph.Metadata{"Type": "host", "Name": "localhost"}) query := `G.V().Has("Name", Regex("^local.*st$")).Count()` tp := traversal.NewGremlinTraversalParser(strings.NewReader(query), g) tp.AddTraversalExtension(NewTopologyTraversalExtension()) ts, err := tp.Parse() if err != nil { t.Fatal(err.Error()) } res, err := ts.Exec() if err != nil { t.Fatal(err.Error()) } if len(res.Values()) != 1 || res.Values()[0].(int) != 1 { t.Fatalf("Regex should exactly match 1 node, returned: %v", res.Values()) } }
func (o *OvsdbProbe) OnOvsPortAdd(monitor *ovsdb.OvsMonitor, uuid string, row *libovsdb.RowUpdate) { o.Lock() defer o.Unlock() o.Graph.Lock() defer o.Graph.Unlock() port, ok := o.uuidToPort[uuid] if !ok { port = o.Graph.NewNode(graph.GenID(), graph.Metadata{ "UUID": uuid, "Name": row.New.Fields["name"].(string), "Type": "ovsport", }) o.uuidToPort[uuid] = port } // bond mode if mode, ok := row.New.Fields["bond_mode"]; ok { switch mode.(type) { case string: o.Graph.AddMetadata(port, "BondMode", mode.(string)) } } // lacp if lacp, ok := row.New.Fields["lacp"]; ok { switch lacp.(type) { case string: o.Graph.AddMetadata(port, "LACP", lacp.(string)) } } // vlan tag if tag, ok := row.New.Fields["tag"]; ok { switch tag.(type) { case libovsdb.OvsSet: set := tag.(libovsdb.OvsSet) if len(set.GoSet) > 0 { o.Graph.AddMetadata(port, "Vlans", set.GoSet) } case float64: o.Graph.AddMetadata(port, "Vlans", int(tag.(float64))) } } switch row.New.Fields["interfaces"].(type) { case libovsdb.OvsSet: set := row.New.Fields["interfaces"].(libovsdb.OvsSet) for _, i := range set.GoSet { u := i.(libovsdb.UUID).GoUUID intf, ok := o.uuidToIntf[u] if ok && !o.Graph.AreLinked(port, intf) { o.Graph.Link(port, intf, graph.Metadata{"RelationType": "layer2"}) } else { /* will be filled later when the interface update for this interface will be triggered */ o.intfPortQueue[u] = port } } case libovsdb.UUID: u := row.New.Fields["interfaces"].(libovsdb.UUID).GoUUID intf, ok := o.uuidToIntf[u] if ok && !o.Graph.AreLinked(port, intf) { o.Graph.Link(port, intf, graph.Metadata{"RelationType": "layer2"}) } else { /* will be filled later when the interface update for this interface will be triggered */ o.intfPortQueue[u] = port } } /* set pending port of a container */ if bridge, ok := o.portBridgeQueue[uuid]; ok { o.Graph.Link(bridge, port, graph.Metadata{"RelationType": "layer2"}) delete(o.portBridgeQueue, uuid) } }
func (o *OvsdbProbe) OnOvsInterfaceAdd(monitor *ovsdb.OvsMonitor, uuid string, row *libovsdb.RowUpdate) { o.Lock() defer o.Unlock() // NOTE(safchain) is it a workaround ???, seems that the interface is not fully set switch row.New.Fields["ofport"].(type) { case float64: default: return } var mac string switch row.New.Fields["mac_in_use"].(type) { case string: mac = row.New.Fields["mac_in_use"].(string) } var index int64 if i, ok := row.New.Fields["ifindex"]; ok { switch row.New.Fields["ifindex"].(type) { case float64: index = int64(i.(float64)) case libovsdb.OvsSet: set := row.New.Fields["ifindex"].(libovsdb.OvsSet) if len(set.GoSet) > 0 { index = set.GoSet[0].(int64) } } } var driver string if d, ok := row.New.Fields["status"].(libovsdb.OvsMap).GoMap["driver_name"]; ok { driver = d.(string) } var itype string if t, ok := row.New.Fields["type"]; ok { itype = t.(string) } name := row.New.Fields["name"].(string) o.Graph.Lock() defer o.Graph.Unlock() intf := o.Graph.LookupFirstNode(graph.Metadata{"UUID": uuid}) if intf == nil { // added before by netlink ? intf = o.Graph.LookupFirstNode(graph.Metadata{"Name": name, "Driver": "openvswitch"}) if intf != nil { o.Graph.AddMetadata(intf, "UUID", uuid) } } if intf == nil { // didn't find with the UUID nor with the driver, try with index and/or mac lm := graph.Metadata{"Name": name} if index > 0 { lm["IfIndex"] = index } if mac != "" { lm["MAC"] = mac } if len(lm) > 1 { intf = o.Graph.LookupFirstChild(o.Root, lm) if intf != nil { o.Graph.AddMetadata(intf, "UUID", uuid) } } } if intf == nil { intf = o.Graph.NewNode(graph.GenID(), graph.Metadata{"Name": name, "UUID": uuid}) } else if index > 0 { // the index can be added after the interface creation, during an update so // we need to check whether a interface with the same index exists at the first level // if this interface has no UUID it means that it has been added by NETLINK // and this is the same interface thus replace one by the other to keep only // one interface. nodes := o.Graph.LookupChildren(o.Root, graph.Metadata{"Name": name, "IfIndex": index}) for _, node := range nodes { if node.Metadata()["UUID"] != uuid { m := node.Metadata() m["UUID"] = uuid intf = o.Graph.Replace(node, intf) } } } tr := o.Graph.StartMetadataTransaction(intf) defer tr.Commit() if index > 0 { tr.AddMetadata("IfIndex", index) } if mac != "" { tr.AddMetadata("MAC", mac) } if driver != "" { tr.AddMetadata("Driver", driver) } if itype != "" { tr.AddMetadata("Type", itype) } ext_ids := row.New.Fields["external_ids"].(libovsdb.OvsMap) for k, v := range ext_ids.GoMap { tr.AddMetadata("ExtID/"+k.(string), v.(string)) } o.uuidToIntf[uuid] = intf switch itype { case "gre", "vxlan", "geneve": tr.AddMetadata("Driver", "openvswitch") m := row.New.Fields["options"].(libovsdb.OvsMap) if ip, ok := m.GoMap["local_ip"]; ok { tr.AddMetadata("LocalIP", ip.(string)) } if ip, ok := m.GoMap["remote_ip"]; ok { tr.AddMetadata("RemoteIP", ip.(string)) } m = row.New.Fields["status"].(libovsdb.OvsMap) if iface, ok := m.GoMap["tunnel_egress_iface"]; ok { tr.AddMetadata("TunEgressIface", iface.(string)) } if carrier, ok := m.GoMap["tunnel_egress_iface_carrier"]; ok { tr.AddMetadata("TunEgressIfaceCarrier", carrier.(string)) } case "patch": // force the driver as it is not defined and we need it to delete properly tr.AddMetadata("Driver", "openvswitch") m := row.New.Fields["options"].(libovsdb.OvsMap) if p, ok := m.GoMap["peer"]; ok { peerName := p.(string) peer := o.Graph.LookupFirstNode(graph.Metadata{"Name": peerName, "Type": "patch"}) if peer != nil { if !o.Graph.AreLinked(intf, peer) { o.Graph.Link(intf, peer, graph.Metadata{"RelationType": "layer2", "Type": "patch"}) } } else { // lookup in the intf queue for _, peer := range o.uuidToIntf { if peer.Metadata()["Name"] == peerName && !o.Graph.AreLinked(intf, peer) { o.Graph.Link(intf, peer, graph.Metadata{"RelationType": "layer2", "Type": "patch"}) } } } } } /* set pending interface for a port */ if port, ok := o.intfPortQueue[uuid]; ok { o.Graph.Link(port, intf, graph.Metadata{"RelationType": "layer2"}) delete(o.intfPortQueue, uuid) } }
func (u *NetNSProbe) Register(path string, extraMetadata graph.Metadata) *graph.Node { // When a new network namespace has been seen by inotify, the path to // the namespace may still be a regular file, not a bind mount to the // file in /proc/<pid>/tasks/<tid>/ns/net yet, so we wait a bit for the // bind mount to be set up var newns *NetNs err := common.Retry(func() error { var stats syscall.Stat_t fd, err := syscall.Open(path, syscall.O_RDONLY, 0) if err != nil { return err } err = syscall.Fstat(fd, &stats) syscall.Close(fd) if err != nil { return err } if stats.Dev != u.rootNsDev { return fmt.Errorf("%s does not seem to be a valid namespace", path) } newns = &NetNs{path: path, dev: stats.Dev, ino: stats.Ino} return nil }, 10, time.Millisecond*20) if err != nil { logging.GetLogger().Errorf("Could not register namespace: %s", err.Error()) return nil } _, ok := u.pathToNetNS[path] if !ok { u.pathToNetNS[path] = newns } u.Lock() defer u.Unlock() nsString := newns.String() probe, ok := u.nsnlProbes[nsString] if ok { probe.useCount++ logging.GetLogger().Debugf("Increasing counter for namespace %s to %d", nsString, probe.useCount) return probe.Root } u.Graph.Lock() defer u.Graph.Unlock() logging.GetLogger().Debugf("Network Namespace added: %s", nsString) metadata := graph.Metadata{"Name": getNetNSName(path), "Type": "netns", "Path": path} if extraMetadata != nil { for k, v := range extraMetadata { metadata[k] = v } } n := u.Graph.NewNode(graph.GenID(), metadata) u.Graph.Link(u.Root, n, graph.Metadata{"RelationType": "ownership"}) nu := NewNetNsNetLinkTopoUpdater(u.Graph, n) nu.Start(newns) u.nsnlProbes[nsString] = nu return n }