Add grouping algorithm
This might not be fully correct, but it seems to be accurate so far. Of particular note, the vertex order needs to be deterministic for this algorithm, which isn't provided by a map, since golang intentionally randomizes it. As a result, this also adds a sorted version of GetVertices called GetVerticesSorted.
This commit is contained in:
51
config.go
51
config.go
@@ -337,7 +337,7 @@ func (ag *baseGrouper) init(g *Graph) error {
|
|||||||
return fmt.Errorf("The init method has already been called!")
|
return fmt.Errorf("The init method has already been called!")
|
||||||
}
|
}
|
||||||
ag.graph = g // pointer
|
ag.graph = g // pointer
|
||||||
ag.vertices = ag.graph.GetVertices() // cache
|
ag.vertices = ag.graph.GetVerticesSorted() // cache in deterministic order!
|
||||||
ag.i = 0
|
ag.i = 0
|
||||||
ag.j = 0
|
ag.j = 0
|
||||||
if len(ag.vertices) == 0 { // empty graph
|
if len(ag.vertices) == 0 { // empty graph
|
||||||
@@ -437,23 +437,43 @@ func (ag *baseGrouper) vertexTest(b bool) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type algorithmNameGrouper struct { // XXX rename me!
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
|
type nonReachabilityGrouper struct {
|
||||||
baseGrouper // "inherit" what we want, and reimplement the rest
|
baseGrouper // "inherit" what we want, and reimplement the rest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *algorithmNameGrouper) name() string {
|
func (ag *nonReachabilityGrouper) name() string {
|
||||||
log.Fatal("Not implemented!") // XXX
|
return "nonReachabilityGrouper"
|
||||||
return "algorithmNameGrouper"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *algorithmNameGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
// this algorithm relies on the observation that if there's a path from a to b,
|
||||||
log.Fatal("Not implemented!") // XXX
|
// then they *can't* be merged (b/c of the existing dependency) so therefore we
|
||||||
// NOTE: you can even build this like this:
|
// merge anything that *doesn't* satisfy this condition or that of the reverse!
|
||||||
//v1, v2, err = ag.baseGrouper.vertexNext() // get all iterable pairs
|
func (ag *nonReachabilityGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
||||||
// ...
|
for {
|
||||||
//ag.baseGrouper.vertexTest(...)
|
v1, v2, err = ag.baseGrouper.vertexNext() // get all iterable pairs
|
||||||
//return
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("Not implemented!")
|
log.Fatalf("Error running autoGroup(vertexNext): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v1 != v2 { // ignore self cmp early (perf optimization)
|
||||||
|
// if NOT reachable, they're viable...
|
||||||
|
out1 := ag.graph.Reachability(v1, v2)
|
||||||
|
out2 := ag.graph.Reachability(v2, v1)
|
||||||
|
if len(out1) == 0 && len(out2) == 0 {
|
||||||
|
return // return v1 and v2, they're viable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, it means we're skipping over this candidate!
|
||||||
|
if ok, err := ag.baseGrouper.vertexTest(false); err != nil {
|
||||||
|
log.Fatalf("Error running autoGroup(vertexTest): %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
return nil, nil, nil // done!
|
||||||
|
}
|
||||||
|
|
||||||
|
// the vertexTest passed, so loop and try with a new pair...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// autoGroup is the mechanical auto group "runner" that runs the interface spec
|
// autoGroup is the mechanical auto group "runner" that runs the interface spec
|
||||||
@@ -477,7 +497,9 @@ func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
|||||||
wStr := fmt.Sprintf("%s", w)
|
wStr := fmt.Sprintf("%s", w)
|
||||||
|
|
||||||
if err := ag.vertexCmp(v, w); err != nil { // cmp ?
|
if err := ag.vertexCmp(v, w); err != nil { // cmp ?
|
||||||
|
if DEBUG {
|
||||||
strch <- fmt.Sprintf("Compile: Grouping: !GroupCmp for: %s into %s", wStr, vStr)
|
strch <- fmt.Sprintf("Compile: Grouping: !GroupCmp for: %s into %s", wStr, vStr)
|
||||||
|
}
|
||||||
|
|
||||||
// remove grouped vertex and merge edges (res is safe)
|
// remove grouped vertex and merge edges (res is safe)
|
||||||
} else if err := g.VertexMerge(v, w, ag.vertexMerge, ag.edgeMerge); err != nil { // merge...
|
} else if err := g.VertexMerge(v, w, ag.vertexMerge, ag.edgeMerge); err != nil { // merge...
|
||||||
@@ -506,7 +528,8 @@ func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
|||||||
func (g *Graph) AutoGroup() {
|
func (g *Graph) AutoGroup() {
|
||||||
// receive log messages from channel...
|
// receive log messages from channel...
|
||||||
// this allows test cases to avoid printing them when they're unwanted!
|
// this allows test cases to avoid printing them when they're unwanted!
|
||||||
for str := range g.autoGroup(&baseGrouper{}) {
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
|
for str := range g.autoGroup(&nonReachabilityGrouper{}) {
|
||||||
log.Println(str)
|
log.Println(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -155,7 +155,7 @@ func run(c *cli.Context) {
|
|||||||
G = fullGraph.Copy() // copy to active graph
|
G = fullGraph.Copy() // copy to active graph
|
||||||
// XXX: do etcd transaction out here...
|
// XXX: do etcd transaction out here...
|
||||||
G.AutoEdges() // add autoedges; modifies the graph
|
G.AutoEdges() // add autoedges; modifies the graph
|
||||||
//G.AutoGroup() // run autogroup; modifies the graph // TODO
|
G.AutoGroup() // run autogroup; modifies the graph
|
||||||
// TODO: do we want to do a transitive reduction?
|
// TODO: do we want to do a transitive reduction?
|
||||||
|
|
||||||
log.Printf("Graph: %v", G) // show graph
|
log.Printf("Graph: %v", G) // show graph
|
||||||
|
|||||||
57
pgraph.go
57
pgraph.go
@@ -25,6 +25,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -183,7 +184,8 @@ func (g *Graph) NumEdges() int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// get an array (slice) of all vertices in the graph
|
// GetVertices returns a randomly sorted slice of all vertices in the graph
|
||||||
|
// The order is random, because the map implementation is intentionally so!
|
||||||
func (g *Graph) GetVertices() []*Vertex {
|
func (g *Graph) GetVertices() []*Vertex {
|
||||||
var vertices []*Vertex
|
var vertices []*Vertex
|
||||||
for k := range g.Adjacency {
|
for k := range g.Adjacency {
|
||||||
@@ -204,6 +206,23 @@ func (g *Graph) GetVerticesChan() chan *Vertex {
|
|||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VertexSlice []*Vertex
|
||||||
|
|
||||||
|
func (vs VertexSlice) Len() int { return len(vs) }
|
||||||
|
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||||
|
func (vs VertexSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String() }
|
||||||
|
|
||||||
|
// GetVerticesSorted returns a sorted slice of all vertices in the graph
|
||||||
|
// The order is sorted by String() to avoid the non-determinism in the map type
|
||||||
|
func (g *Graph) GetVerticesSorted() []*Vertex {
|
||||||
|
var vertices []*Vertex
|
||||||
|
for k := range g.Adjacency {
|
||||||
|
vertices = append(vertices, k)
|
||||||
|
}
|
||||||
|
sort.Sort(VertexSlice(vertices)) // add determinism
|
||||||
|
return vertices
|
||||||
|
}
|
||||||
|
|
||||||
// make the graph pretty print
|
// make the graph pretty print
|
||||||
func (g *Graph) String() string {
|
func (g *Graph) String() string {
|
||||||
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
||||||
@@ -546,22 +565,54 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
// 2) edges that point towards v2 from X now point to v1 from X (no dupes)
|
// 2) edges that point towards v2 from X now point to v1 from X (no dupes)
|
||||||
for _, x := range g.IncomingGraphEdges(v2) { // all to vertex v (??? -> v)
|
for _, x := range g.IncomingGraphEdges(v2) { // all to vertex v (??? -> v)
|
||||||
e := g.Adjacency[x][v2] // previous edge
|
e := g.Adjacency[x][v2] // previous edge
|
||||||
|
r := g.Reachability(x, v1)
|
||||||
// merge e with ex := g.Adjacency[x][v1] if it exists!
|
// merge e with ex := g.Adjacency[x][v1] if it exists!
|
||||||
if ex, exists := g.Adjacency[x][v1]; exists && edgeMergeFn != nil {
|
if ex, exists := g.Adjacency[x][v1]; exists && edgeMergeFn != nil && len(r) == 0 {
|
||||||
e = edgeMergeFn(e, ex)
|
e = edgeMergeFn(e, ex)
|
||||||
}
|
}
|
||||||
|
if len(r) == 0 { // if not reachable, add it
|
||||||
g.AddEdge(x, v1, e) // overwrite edge
|
g.AddEdge(x, v1, e) // overwrite edge
|
||||||
|
} else if edgeMergeFn != nil { // reachable, merge e through...
|
||||||
|
prev := x // initial condition
|
||||||
|
for i, next := range r {
|
||||||
|
if i == 0 {
|
||||||
|
// next == prev, therefore skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// this edge is from: prev, to: next
|
||||||
|
ex, _ := g.Adjacency[prev][next] // get
|
||||||
|
ex = edgeMergeFn(ex, e)
|
||||||
|
g.Adjacency[prev][next] = ex // set
|
||||||
|
prev = next
|
||||||
|
}
|
||||||
|
}
|
||||||
delete(g.Adjacency[x], v2) // delete old edge
|
delete(g.Adjacency[x], v2) // delete old edge
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) edges that point from v2 to X now point from v1 to X (no dupes)
|
// 3) edges that point from v2 to X now point from v1 to X (no dupes)
|
||||||
for _, x := range g.OutgoingGraphEdges(v2) { // all from vertex v (v -> ???)
|
for _, x := range g.OutgoingGraphEdges(v2) { // all from vertex v (v -> ???)
|
||||||
e := g.Adjacency[v2][x] // previous edge
|
e := g.Adjacency[v2][x] // previous edge
|
||||||
|
r := g.Reachability(v1, x)
|
||||||
// merge e with ex := g.Adjacency[v1][x] if it exists!
|
// merge e with ex := g.Adjacency[v1][x] if it exists!
|
||||||
if ex, exists := g.Adjacency[v1][x]; exists && edgeMergeFn != nil {
|
if ex, exists := g.Adjacency[v1][x]; exists && edgeMergeFn != nil && len(r) == 0 {
|
||||||
e = edgeMergeFn(e, ex)
|
e = edgeMergeFn(e, ex)
|
||||||
}
|
}
|
||||||
|
if len(r) == 0 {
|
||||||
g.AddEdge(v1, x, e) // overwrite edge
|
g.AddEdge(v1, x, e) // overwrite edge
|
||||||
|
} else if edgeMergeFn != nil { // reachable, merge e through...
|
||||||
|
prev := v1 // initial condition
|
||||||
|
for i, next := range r {
|
||||||
|
if i == 0 {
|
||||||
|
// next == prev, therefore skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// this edge is from: prev, to: next
|
||||||
|
ex, _ := g.Adjacency[prev][next]
|
||||||
|
ex = edgeMergeFn(ex, e)
|
||||||
|
g.Adjacency[prev][next] = ex
|
||||||
|
prev = next
|
||||||
|
}
|
||||||
|
}
|
||||||
delete(g.Adjacency[v2], x)
|
delete(g.Adjacency[v2], x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ func (obj *NoopResTest) GroupCmp(r Res) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement this in vertexCmp for *testBaseGrouper instead?
|
// TODO: implement this in vertexCmp for *testGrouper instead?
|
||||||
if strings.Contains(res.Name, ",") { // HACK
|
if strings.Contains(res.Name, ",") { // HACK
|
||||||
return false // element to be grouped is already grouped!
|
return false // element to be grouped is already grouped!
|
||||||
}
|
}
|
||||||
@@ -755,15 +755,16 @@ Loop:
|
|||||||
return nil // success!
|
return nil // success!
|
||||||
}
|
}
|
||||||
|
|
||||||
type testBaseGrouper struct { // FIXME: update me when we've implemented the correct grouping algorithm!
|
type testGrouper struct {
|
||||||
baseGrouper // "inherit" what we want, and reimplement the rest
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
|
nonReachabilityGrouper // "inherit" what we want, and reimplement the rest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *testBaseGrouper) name() string {
|
func (ag *testGrouper) name() string {
|
||||||
return "testBaseGrouper"
|
return "testGrouper"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *testBaseGrouper) vertexMerge(v1, v2 *Vertex) (v *Vertex, err error) {
|
func (ag *testGrouper) vertexMerge(v1, v2 *Vertex) (v *Vertex, err error) {
|
||||||
if err := v1.Res.GroupRes(v2.Res); err != nil { // group them first
|
if err := v1.Res.GroupRes(v2.Res); err != nil { // group them first
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -779,7 +780,7 @@ func (ag *testBaseGrouper) vertexMerge(v1, v2 *Vertex) (v *Vertex, err error) {
|
|||||||
return // success or fail, and no need to merge the actual vertices!
|
return // success or fail, and no need to merge the actual vertices!
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *testBaseGrouper) edgeMerge(e1, e2 *Edge) *Edge {
|
func (ag *testGrouper) edgeMerge(e1, e2 *Edge) *Edge {
|
||||||
// HACK: update the name so it makes a union of both names
|
// HACK: update the name so it makes a union of both names
|
||||||
n1 := strings.Split(e1.Name, ",") // load
|
n1 := strings.Split(e1.Name, ",") // load
|
||||||
n2 := strings.Split(e2.Name, ",") // load
|
n2 := strings.Split(e2.Name, ",") // load
|
||||||
@@ -805,8 +806,7 @@ func (g *Graph) fullPrint() (str string) {
|
|||||||
|
|
||||||
// helper function
|
// helper function
|
||||||
func runGraphCmp(t *testing.T, g1, g2 *Graph) {
|
func runGraphCmp(t *testing.T, g1, g2 *Graph) {
|
||||||
// FIXME: update me when we've implemented the correct grouping algorithm!
|
ch := g1.autoGroup(&testGrouper{}) // edits the graph
|
||||||
ch := g1.autoGroup(&testBaseGrouper{}) // edits the graph
|
|
||||||
for _ = range ch { // bleed the channel or it won't run :(
|
for _ = range ch { // bleed the channel or it won't run :(
|
||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
@@ -1126,13 +1126,14 @@ func TestPgraphGrouping15(t *testing.T) {
|
|||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FIXME: uncomment me when we've implemented the correct grouping algorithm!
|
// re-attach 1 (outer)
|
||||||
// reattach 1 (outer)
|
// technically the second possibility is valid too, depending on which order we
|
||||||
// a1 a2 a1,a2
|
// merge edges in, and if we don't filter out any unnecessary edges afterwards!
|
||||||
// | / |
|
// a1 a2 a1,a2 a1,a2
|
||||||
// b1 / >>> b1 (arrows point downwards)
|
// | / | | \
|
||||||
// | / |
|
// b1 / >>> b1 OR b1 / (arrows point downwards)
|
||||||
// c1 c1
|
// | / | | /
|
||||||
|
// c1 c1 c1
|
||||||
func TestPgraphGrouping16(t *testing.T) {
|
func TestPgraphGrouping16(t *testing.T) {
|
||||||
g1 := NewGraph("g1") // original graph
|
g1 := NewGraph("g1") // original graph
|
||||||
{
|
{
|
||||||
@@ -1153,14 +1154,14 @@ func TestPgraphGrouping16(t *testing.T) {
|
|||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
b1 := NewVertex(NewNoopResTest("b1"))
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
c1 := NewVertex(NewNoopResTest("c1"))
|
||||||
e1 := NewEdge("e1,e3")
|
e1 := NewEdge("e1,e3")
|
||||||
e2 := NewEdge("e2") // TODO: should this be e2,e3 (eg we split e3?)
|
e2 := NewEdge("e2,e3") // e3 gets "merged through" to BOTH edges!
|
||||||
g2.AddEdge(a, b1, e1)
|
g2.AddEdge(a, b1, e1)
|
||||||
g2.AddEdge(b1, c1, e2)
|
g2.AddEdge(b1, c1, e2)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reattach 2 (inner)
|
// re-attach 2 (inner)
|
||||||
// a1 b2 a1
|
// a1 b2 a1
|
||||||
// | / |
|
// | / |
|
||||||
// b1 / >>> b1,b2 (arrows point downwards)
|
// b1 / >>> b1,b2 (arrows point downwards)
|
||||||
@@ -1194,6 +1195,7 @@ func TestPgraphGrouping17(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// re-attach 3 (double)
|
// re-attach 3 (double)
|
||||||
|
// similar to "re-attach 1", technically there is a second possibility for this
|
||||||
// a2 a1 b2 a1,a2
|
// a2 a1 b2 a1,a2
|
||||||
// \ | / |
|
// \ | / |
|
||||||
// \ b1 / >>> b1,b2 (arrows point downwards)
|
// \ b1 / >>> b1,b2 (arrows point downwards)
|
||||||
@@ -1222,18 +1224,18 @@ func TestPgraphGrouping18(t *testing.T) {
|
|||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
b := NewVertex(NewNoopResTest("b1,b2"))
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
c1 := NewVertex(NewNoopResTest("c1"))
|
||||||
e1 := NewEdge("e1,e3")
|
e1 := NewEdge("e1,e3")
|
||||||
e2 := NewEdge("e2,e4")
|
e2 := NewEdge("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
||||||
g2.AddEdge(a, b, e1)
|
g2.AddEdge(a, b, e1)
|
||||||
g2.AddEdge(b, c1, e2)
|
g2.AddEdge(b, c1, e2)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tricky merge, (no change or merge?)
|
// connected merge 0, (no change!)
|
||||||
// a1 a1
|
// a1 a1
|
||||||
// \ >>> \ (arrows point downwards)
|
// \ >>> \ (arrows point downwards)
|
||||||
// a2 a2
|
// a2 a2
|
||||||
func TestPgraphGroupingTricky1(t *testing.T) {
|
func TestPgraphGroupingConnected0(t *testing.T) {
|
||||||
g1 := NewGraph("g1") // original graph
|
g1 := NewGraph("g1") // original graph
|
||||||
{
|
{
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
a1 := NewVertex(NewNoopResTest("a1"))
|
||||||
@@ -1248,11 +1250,35 @@ func TestPgraphGroupingTricky1(t *testing.T) {
|
|||||||
e1 := NewEdge("e1")
|
e1 := NewEdge("e1")
|
||||||
g2.AddEdge(a1, a2, e1)
|
g2.AddEdge(a1, a2, e1)
|
||||||
}
|
}
|
||||||
//g3 := NewGraph("g2") // expected result ?
|
runGraphCmp(t, g1, g2)
|
||||||
//{
|
}
|
||||||
// a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
//}
|
// connected merge 1, (no change!)
|
||||||
runGraphCmp(t, g1, g2) // TODO: i'm tempted to think this is correct
|
// a1 a1
|
||||||
//runGraphCmp(t, g1, g3)
|
// \ \
|
||||||
|
// b >>> b (arrows point downwards)
|
||||||
|
// \ \
|
||||||
|
// a2 a2
|
||||||
|
func TestPgraphGroupingConnected1(t *testing.T) {
|
||||||
|
g1 := NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := NewVertex(NewNoopResTest("b"))
|
||||||
|
a2 := NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := NewEdge("e1")
|
||||||
|
e2 := NewEdge("e2")
|
||||||
|
g1.AddEdge(a1, b, e1)
|
||||||
|
g1.AddEdge(b, a2, e2)
|
||||||
|
}
|
||||||
|
g2 := NewGraph("g2") // expected result ?
|
||||||
|
{
|
||||||
|
a1 := NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := NewVertex(NewNoopResTest("b"))
|
||||||
|
a2 := NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := NewEdge("e1")
|
||||||
|
e2 := NewEdge("e2")
|
||||||
|
g2.AddEdge(a1, b, e1)
|
||||||
|
g2.AddEdge(b, a2, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user