func (spec specCommit) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "version", } e.Props = e.Props.Set("sha1", system.Property{MsgSrc: mid, Value: spec.Sha1}) re := g.OutWith(src.ID, q.Qbe(system.EType("version"))) if len(re) > 0 { sha1, _ := re[0].Props.Lookup("sha1") e.ID = re[0].ID // FIXME setting the id to non-0 AND failing is currently unhandled if sha1.(system.Property).Value == spec.Sha1 { success = true e.Target = re[0].Target } else { rv := g.VerticesWith(q.Qbv(system.VType("commit"), "sha1", spec.Sha1)) if len(rv) == 1 { success = true e.Target = rv[0].ID } } } else { rv := g.VerticesWith(q.Qbv(system.VType("commit"), "sha1", spec.Sha1)) if len(rv) == 1 { success = true e.Target = rv[0].ID } } return }
func (spec specUnixDomainListener) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { // check for existing edge; this one is quite straightforward re := g.OutWith(src.ID, q.Qbe(system.EType("listening"), "type", "unix", "path", spec.Path)) if len(re) == 1 { return re[0], true } e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "listening", } e.Props = e.Props.Set("path", system.Property{MsgSrc: mid, Value: spec.Path}) envid, _, hasenv := findEnv(g, src) if hasenv { rv := g.PredecessorsWith(envid, q.Qbv(system.VType("comm"), "type", "unix", "path", spec.Path)) if len(rv) == 1 { success = true e.Target = rv[0].ID } } return }
func (spec specParentDataset) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "dataset-gateway", } e.Props = e.Props.Set("name", system.Property{MsgSrc: mid, Value: spec.Name}) // check for existing link - there can be only be one re := g.OutWith(src.ID, q.Qbe(system.EType("dataset-gateway"))) if len(re) == 1 { success = true e = re[0] // TODO semantics should preclude this from being able to change, but doing it dirty means force-setting it anyway for now } else { // no existing link found; search for proc directly envid, _, _ := findEnv(g, src) rv := g.PredecessorsWith(envid, q.Qbv(system.VType("parent-dataset"), "name", spec.Name)) if len(rv) != 0 { // >1 shouldn't be possible success = true e.Target = rv[0].ID } } return }
func (spec specGitCommitParent) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "parent-commit", } re := g.OutWith(src.ID, q.Qbe(system.EType("parent-commit"), "pnum", spec.ParentNum)) if len(re) > 0 { success = true e.Target = re[0].Target e.Props = re[0].Props // FIXME evidence of a problem here - since we're using pnum as the deduping identifier, there's no // way it could also sensibly change its MsgSrc value. This is very much a product of the intensional/extensional // identity problem: what does it mean to have the identifying data change? is it now a new thing? was it the old thing, // and it underwent a transition into the new thing? or is there no distinction between the old and new thing? e.Props = e.Props.Set("sha1", system.Property{MsgSrc: mid, Value: spec.Sha1}) e.ID = re[0].ID } else { rv := g.VerticesWith(q.Qbv(system.VType("commit"), "sha1", spec.Sha1)) if len(rv) == 1 { success = true e.Target = rv[0].ID e.Props = e.Props.Set("pnum", system.Property{MsgSrc: mid, Value: spec.ParentNum}) e.Props = e.Props.Set("sha1", system.Property{MsgSrc: mid, Value: spec.Sha1}) } } return }
func (spec EnvLink) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { _, e, success = findEnv(g, src) // Whether we find a match or not, have to merge in the EnvLink e.Props = maputil.FillPropMap(mid, false, pp("hostname", spec.Address.Hostname), pp("ipv4", spec.Address.Ipv4), pp("ipv6", spec.Address.Ipv6), pp("nick", spec.Nick), ) // If we already found the matching edge, bail out now if success { return } rv := g.VerticesWith(q.Qbv(system.VType("environment"))) for _, vt := range rv { // TODO this'll be cross-package eventually - reorg needed if maputil.AnyMatch(e.Props, vt.Vertex.Properties, "nick", "hostname", "ipv4", "ipv6") { success = true e.Target = vt.ID break } } return }
func (spec specLocalLogic) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "logic-link", } // search for existing link re := g.OutWith(src.ID, q.Qbe(system.EType("logic-link"), "path", spec.Path)) if len(re) == 1 { // TODO don't set the path prop again, it's the unique id...meh, same question here w/uniqueness as above success = true e = re[0] return } // no existing link found, search for proc directly envid, _, _ := findEnv(g, src) rv := g.PredecessorsWith(envid, q.Qbv(system.VType("logic-state"), "path", spec.Path)) if len(rv) == 1 { success = true e.Target = rv[0].ID } return }
func commitUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { candidates := g.VerticesWith(q.Qbv(system.VType("commit"), "sha1", u.Vertex().Properties()["sha1"])) if len(candidates) > 0 { // there can be only one return candidates[0].ID } return 0 }
func processUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { // only one scoping edge - the envlink edge, success := u.ScopingSpecs()[0].(EnvLink).Resolve(g, 0, emptyVT(u.Vertex())) if !success { // FIXME scoping edge resolution failure does not mean no match - there could be an orphan return 0 } return findMatchingEnvId(g, edge, g.VerticesWith(q.Qbv(system.VType("process"), "pid", u.Vertex().Properties()["pid"]))) }
func findEnvironment(g system.CoreGraph, props ps.Map) (envid uint64, success bool) { rv := g.VerticesWith(q.Qbv(system.VType("environment"))) for _, vt := range rv { if maputil.AnyMatch(props, vt.Vertex.Props(), "hostname", "ipv4", "ipv6", "nick") { return vt.ID, true } } return }
func parentDatasetUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { edge, success := u.ScopingSpecs()[0].(EnvLink).Resolve(g, 0, emptyVT(u.Vertex())) if !success { // FIXME scoping edge resolution failure does not mean no match - there could be an orphan return 0 } props := u.Vertex().Properties() return findMatchingEnvId(g, edge, g.VerticesWith(q.Qbv(system.VType("parent-dataset"), "path", props["path"], "name", props["name"]))) }
func envUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { matches := g.VerticesWith(q.Qbv(system.VType("environment"))) for _, e := range matches { if maputil.AnyMatch(e.Vertex.Properties, u.Vertex().Properties(), "hostname", "ipv4", "ipv6") { return e.ID } } return 0 }
func TestVerticesWith(t *testing.T) { g := getGraphFixture() var result system.VertexTupleVector result = g.VerticesWith(q.Qbv()) if len(result) != 5 { t.Errorf("Should find 4 vertices with no filter; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VType("env"))) if len(result) != 2 { t.Errorf("Should find 2 vertices when filtering to type env; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VType("nonexistent-type"))) if len(result) != 0 { t.Errorf("Should find no vertices when filtering on type that's not present; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VTypeNone, "prop1", "bar")) if len(result) != 2 { t.Errorf("Should find two vertices with prop1 == \"bar\"; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VTypeNone, "none-have-this-prop-key", "doesn't matter")) if len(result) != 0 { t.Errorf("Should find no vertices when filtering on nonexistent prop key; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VType("env"), "prop1", "foo")) if len(result) != 1 { t.Errorf("Should find one vertex when filtering to env types and with prop1 == \"foo\"; found %v", len(result)) } result = g.VerticesWith(q.Qbv(system.VType("env"), "prop2", 42)) if len(result) != 1 { t.Errorf("Should find one vertex when filtering to env types and with prop2 == 42; found %v", len(result)) } }
func commUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { // only one scoping edge - the envlink edge, success := u.ScopingSpecs()[0].(EnvLink).Resolve(g, 0, emptyVT(u.Vertex())) if !success { // FIXME scoping edge resolution failure does not mean no match - there could be an orphan return 0 } vp := u.Vertex().Properties() typ, _ := vp["type"] path, haspath := vp["path"] if haspath { return findMatchingEnvId(g, edge, g.VerticesWith(q.Qbv(system.VType("comm"), "type", typ, "path", path))) } else { port, _ := vp["port"] return findMatchingEnvId(g, edge, g.VerticesWith(q.Qbv(system.VType("comm"), "type", typ, "port", port))) } }
func pkgYumUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { props := u.Vertex().Properties() vtv := g.VerticesWith(q.Qbv(system.VType("pkg-yum"), "name", props["name"], "version", props["version"], "arch", props["arch"], "epoch", props["epoch"], )) if len(vtv) > 0 { return vtv[0].ID } return 0 }
func TestQbv(t *testing.T) { // ensure implement both VFilter and EFilter interfaces, and the V chainer var _ system.VFilterChain = vertexFilter{} var _ system.VEFilter = vertexFilter{} assert.Equal(t, Qbv(), vertexFilter{}, "qbv with no args creates an empty vertexFilter") assert.Equal(t, Qbv(), vertexFilter{vtype: system.VTypeNone}, "qbv with no args creates equivalent of passing VTypeNone as first arg") assert.Equal(t, Qbv(system.VType("foo")), vertexFilter{vtype: system.VType("foo")}, "qbv with single arg assigns to VType struct prop") assert.Equal(t, Qbv(system.VTypeNone, "foo"), vertexFilter{vtype: system.VTypeNone}, "qbv with two args ignores second (unpaired) arg") assert.Equal(t, Qbv(system.VTypeNone, "foo", "bar"), vertexFilter{vtype: system.VTypeNone, props: []system.PropPair{{"foo", "bar"}}}, "qbv with three args creates one pair of second (key) and third (value) args") assert.Equal(t, Qbv(system.VTypeNone, "foo", "bar", "baz"), vertexFilter{vtype: system.VTypeNone, props: []system.PropPair{{"foo", "bar"}}}, "qbv with four args creates one pair from 2nd and 3rd args, ignores 4th") // ensure that some incorrect things owing to loose typing correctly panic assert.Panics(t, func() { Qbv("foo") }, "qbv panics on type conversion when passing a string instead of VType") assert.Panics(t, func() { Qbv(system.VTypeNone, 1, "foo") }, "qbv panics on type conversion when second argument (with corresponding pair val 3rd arg) is non-string") assert.Panics(t, func() { Qbv(system.VTypeNone, "foo", "bar", 1, "baz") }, "qbv panics on type conversion when Nth even argument (with corresponding pair val N+1 arg) is non-string") }
func datasetUnify(g system.CoreGraph, u system.UnifyInstructionForm) uint64 { vtv := g.VerticesWith(q.Qbv(system.VType("dataset"), "name", u.Vertex().Properties()["name"])) if len(vtv) == 0 { return 0 } spec := u.ScopingSpecs()[0].(specDatasetHierarchy) el, success := spec.Environment.Resolve(g, 0, emptyVT(u.Vertex())) // FIXME scoping edge resolution failure does not mean no match - there could be an orphan if success { for _, vt := range vtv { if id := findMatchingEnvId(g, el, g.SuccessorsWith(vt.ID, q.Qbe(system.EType("dataset-hierarchy")))); id != 0 { return vt.ID } } } return 0 }
func findDataset(g system.CoreGraph, envid uint64, name []string) (id uint64, success bool) { // first time through use the parent type vtype := system.VType("parent-dataset") id = envid var n string for len(name) > 0 { n, name = name[0], name[1:] rv := g.PredecessorsWith(id, q.Qbv(vtype, "name", n)) vtype = "dataset" if len(rv) != 1 { return 0, false } id = rv[0].ID } return id, true }
// Tests adjacentWith(), which effectively tests SuccessorsWith() and PredecessorsWith() func TestAdjacentWith(t *testing.T) { g := getGraphFixture() var result system.VertexTupleVector // basic, unfiltered tests first to ensure the right data is coming through // vtx 2 has just one in-edge result = g.adjacentWith(2, q.Qbv(), true) if len(result) != 1 { t.Errorf("Vertex 2 has one predecessor, but got %v vertices", len(result)) } result = g.PredecessorsWith(2, q.Qbv()) if len(result) != 1 { t.Errorf("Vertex 2 has one predecessor, but got %v vertices", len(result)) } // vtx 1 has one out-edge and one in-edge result = g.adjacentWith(1, q.Qbv(), false) if len(result) != 1 { t.Errorf("Vertex 1 has one successor, but got %v vertices", len(result)) } result = g.SuccessorsWith(1, q.Qbv()) if len(result) != 1 { t.Errorf("Vertex 1 has one successor, but got %v vertices", len(result)) } // vtx 5 is an isolate result = g.adjacentWith(5, q.Qbv(), true) if len(result) != 0 { t.Errorf("Vertex 5 has no predecessors, but got %v vertices", len(result)) } result = g.PredecessorsWith(5, q.Qbv()) if len(result) != 0 { t.Errorf("Vertex 5 has no predecessors, but got %v vertices", len(result)) } result = g.adjacentWith(5, q.Qbv(), false) if len(result) != 0 { t.Errorf("Vertex 5 has no successors, but got %v vertices", len(result)) } result = g.SuccessorsWith(5, q.Qbv()) if len(result) != 0 { t.Errorf("Vertex 5 has no successors, but got %v vertices", len(result)) } // qbe w/out args should be equivalent result = g.PredecessorsWith(2, q.Qbe()) if len(result) != 1 { t.Errorf("Vertex 2 has one predecessor, but got %v vertices (qbe)", len(result)) } result = g.SuccessorsWith(1, q.Qbe()) if len(result) != 1 { t.Errorf("Vertex 1 has one successor, but got %v vertices (qbe)", len(result)) } // deduping: vtx 4 has two in-edges and none out, but those edges are parallel so only one unique vtx result = g.PredecessorsWith(4, q.Qbv()) if len(result) != 1 { t.Errorf("Vertex 4 has two in-edges, but only one unique predecessor; however, got %v vertices", len(result)) } // vtx 3 is on the other side of vtx 4 - three out-edges, but only two uniques result = g.SuccessorsWith(3, q.Qbv()) if len(result) != 2 { t.Errorf("Vertex 4 has three out-edges, but only two unique successors; however, got %v vertices", len(result)) } // filter checks, beginning with edge and/or vertex typing result = g.SuccessorsWith(3, q.Qbv(system.VType("vt3"))) if len(result) != 1 { t.Errorf("Vertex 4 has only one unique successor of type \"vt3\"; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.EType("dummy-edge-type2"))) if len(result) != 2 { t.Errorf("Vertex 4 has two out-edges of \"dummy-edge-type2\" and both point to different vertices, so expecting 2, but got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.EType("dummy-edge-type2")).And(q.Qbv(system.VType("env")))) if len(result) != 1 { t.Errorf("Vertex 4 has two unique successors along \"dummy-edge-type2\" out-edges, but only one is vtype \"env\". However, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.EType("dummy-edge-type3")).And(q.Qbv(system.VType("env")))) if len(result) != 0 { t.Errorf("Vertex 4 has one unique successor along \"dummy-edge-type3\" out-edges, but it is not an \"env\" type. However, got %v vertices", len(result)) } // prop-filtering checks result = g.SuccessorsWith(3, q.Qbv(system.VTypeNone, "prop2", 42)) if len(result) != 2 { t.Errorf("Vertex 4 has only two unique successors with \"prop2\" at 42; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.ETypeNone, "eprop2", "bar")) if len(result) != 2 { t.Errorf("Vertex 4 has two unique successors connected by two out-edges with \"eprop2\" at \"bar\"; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbv(system.VTypeNone, "prop1", "baz", "prop2", 42)) if len(result) != 1 { t.Errorf("Vertex 4 has only one unique successor with \"prop1\" at \"baz\" and \"prop2\" at 42; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbv(system.VTypeNone, "prop3", "qux", "prop2", 42)) if len(result) != 1 { t.Errorf("Vertex 4 has only one unique successor with BOTH \"prop3\" at \"qux\" and \"prop2\" at 42; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.ETypeNone, "eprop2", "bar").And(q.Qbv(system.VTypeNone, "prop1", "baz"))) if len(result) != 1 { t.Errorf("Vertex 4 has only one unique successor with \"prop1\" at \"baz\" along an out-edge with \"eprop2\" at \"bar\"; however, got %v vertices", len(result)) } result = g.SuccessorsWith(3, q.Qbe(system.ETypeNone, "eprop2", "bar").And(q.Qbv(system.VType("vt3"), "prop1", "baz"))) if len(result) != 1 { t.Errorf("Vertex 4 has one unique successor of type \"vt3\" with \"prop1\" at \"baz\" along an out-edge with \"eprop2\" at \"bar\"; however, got %v vertices", len(result)) } }
func (spec DataLink) Resolve(g system.CoreGraph, mid uint64, src system.VertexTuple) (e system.StdEdge, success bool) { e = system.StdEdge{ Source: src.ID, Props: ps.NewMap(), EType: "datalink", } // DataLinks have a 'name' field that is expected to be unique for the source, if present if spec.Name != "" { // TODO 'name' is a traditional unique key; a change in it inherently denotes a new edge. how to handle this? // FIXME this approach just always updates the mid, which is weird? e.Props = e.Props.Set("name", system.Property{MsgSrc: mid, Value: spec.Name}) re := g.OutWith(src.ID, q.Qbe(system.EType("datalink"), "name", spec.Name)) if len(re) == 1 { success = true e = re[0] } } if spec.Type != "" { e.Props = e.Props.Set("type", system.Property{MsgSrc: mid, Value: spec.Type}) } if spec.Subset != "" { e.Props = e.Props.Set("subset", system.Property{MsgSrc: mid, Value: spec.Subset}) } if spec.Interaction != "" { e.Props = e.Props.Set("interaction", system.Property{MsgSrc: mid, Value: spec.Interaction}) } // Special bits: if we have ConnUnix data, eliminate ConnNet data, and vice-versa. var isLocal bool if spec.ConnUnix.Path != "" { isLocal = true e.Props = e.Props.Set("path", system.Property{MsgSrc: mid, Value: spec.ConnUnix.Path}) e.Props = e.Props.Delete("hostname") e.Props = e.Props.Delete("ipv4") e.Props = e.Props.Delete("ipv6") e.Props = e.Props.Delete("port") e.Props = e.Props.Delete("proto") } else { e.Props = e.Props.Set("port", system.Property{MsgSrc: mid, Value: spec.ConnNet.Port}) e.Props = e.Props.Set("proto", system.Property{MsgSrc: mid, Value: spec.ConnNet.Proto}) // can only be one of hostname, ipv4 or ipv6 if spec.ConnNet.Hostname != "" { e.Props = e.Props.Set("hostname", system.Property{MsgSrc: mid, Value: spec.ConnNet.Hostname}) } else if spec.ConnNet.Ipv4 != "" { e.Props = e.Props.Set("ipv4", system.Property{MsgSrc: mid, Value: spec.ConnNet.Ipv4}) } else { e.Props = e.Props.Set("ipv6", system.Property{MsgSrc: mid, Value: spec.ConnNet.Ipv6}) } } if success { return } var sock system.VertexTuple var rv system.VertexTupleVector // just for reuse // If net, must scan; if local, a bit easier. if !isLocal { // First, find the environment vertex rv = g.VerticesWith(q.Qbv(system.VType("environment"))) var envid uint64 for _, vt := range rv { if maputil.AnyMatch(e.Props, vt.Vertex.Properties, "hostname", "ipv4", "ipv6") { envid = vt.ID break } } // No matching env found, bail out if envid == 0 { return } // Now, walk the environment's edges to find the vertex representing the port rv = g.PredecessorsWith(envid, q.Qbv(system.VType("comm"), "type", "port", "port", spec.ConnNet.Port).And(q.Qbe(system.EType("envlink")))) if len(rv) != 1 { return } sock = rv[0] // With sock in hand, now find its proc rv = g.PredecessorsWith(sock.ID, q.Qbe(system.EType("listening"), "proto", spec.ConnNet.Proto).And(q.Qbv(system.VType("process")))) if len(rv) != 1 { // TODO could/will we ever allow >1? return } } else { envid, _, exists := findEnv(g, src) if !exists { // this is would be a pretty weird case return } // Walk the graph to find the vertex representing the unix socket rv = g.PredecessorsWith(envid, q.Qbv(system.VType("comm"), "path", spec.ConnUnix.Path).And(q.Qbe(system.EType("envlink")))) if len(rv) != 1 { return } sock = rv[0] // With sock in hand, now find its proc rv = g.PredecessorsWith(sock.ID, q.Qbv(system.VType("process")).And(q.Qbe(system.EType("listening")))) if len(rv) != 1 { // TODO could/will we ever allow >1? return } } rv = g.SuccessorsWith(rv[0].ID, q.Qbv(system.VType("parent-dataset"))) // FIXME this absolutely could be more than 1 if len(rv) != 1 { return } dataset := rv[0] // if the spec indicates a subset, find it if spec.Subset != "" { rv = g.PredecessorsWith(rv[0].ID, q.Qbv(system.VType("dataset"), "name", spec.Subset).And(q.Qbe(system.EType("dataset-hierarchy")))) if len(rv) != 1 { return } dataset = rv[0] } // FIXME only recording the final target id is totally broken; see https://github.com/pipeviz/pipeviz/issues/37 // Aaaand we found our target. success = true e.Target = dataset.ID return }