diff --git a/server/server_test.go b/server/server_test.go index 4fcc7f253..126e08ba4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1219,7 +1219,7 @@ func TestClusterCreatedAtRace(t *testing.T) { for _, com := range cluster.Nodes { nodes := com.API.Hosts(context.Background()) for _, n := range nodes { - if n.State != string(disco.NodeStateStarted) { + if n.State != disco.NodeStateStarted { t.Fatalf("unexpected node state (%s) after upping cluster: %v", n.State, nodes) } } @@ -1268,3 +1268,46 @@ func TestClusterCreatedAtRace(t *testing.T) { }) } } + +func TestClusterQueryCountInDegraded(t *testing.T) { + cluster := test.MustNewCluster(t, 3) + for _, c := range cluster.Nodes { + c.Config.Cluster.ReplicaN = 2 + } + err := cluster.Start() + if err != nil { + t.Fatalf("starting cluster: %v", err) + } + defer cluster.Close() + + p := cluster.GetPrimary() + if err := p.Client().CreateIndex(context.Background(), "i", pilosa.IndexOptions{TrackExistence: true}); err != nil { + t.Fatal(err) + } else if err := p.Client().CreateField(context.Background(), "i", "f"); err != nil { + t.Fatal(err) + } + + np := cluster.GetNonPrimary() + // Write some data + for i := 0; i < 10; i++ { + if _, err := np.Query(t, "i", "", fmt.Sprintf(`Set(%d, f=1)`, i*pilosa.ShardWidth+1)); err != nil { + t.Fatal(err) + } + } + + if err := p.Close(); err != nil { + t.Fatal(err) + } + + if err := np.AwaitState(disco.ClusterStateDegraded, 30*time.Second); err != nil { + t.Fatal(err) + } + if resp, err := np.Client().Query(context.Background(), "i", &pilosa.QueryRequest{ + Index: "i", + Query: "Count(All())", + }); err != nil { + t.Fatal(err) + } else { + t.Logf("%+v", resp) + } +} diff --git a/test/cluster.go b/test/cluster.go index c3d0d116b..988c53927 100644 --- a/test/cluster.go +++ b/test/cluster.go @@ -132,11 +132,7 @@ func (c *Cluster) GetNode(n int) *Command { return c.Nodes[ids[n].idx] } -// GetCoordinator gets the node which has been determined to be the coordinator. -// This used to be node0 in tests, but since implementing etcd, the coordinator -// can be any node in the cluster, so we have to use this method in tests which -// need to act on the coordinator. -func (c *Cluster) GetCoordinator() *Command { +func (c *Cluster) GetPrimary() *Command { for _, n := range c.Nodes { if n.IsPrimary() { return n @@ -145,8 +141,7 @@ func (c *Cluster) GetCoordinator() *Command { return nil } -// GetNonCoordinator gets first first non-coordinator node in the list of nodes. -func (c *Cluster) GetNonCoordinator() *Command { +func (c *Cluster) GetNonPrimary() *Command { for _, n := range c.Nodes { if !n.IsPrimary() { return n @@ -155,8 +150,7 @@ func (c *Cluster) GetNonCoordinator() *Command { return nil } -// GetNonCoordinators gets all nodes except the coordinator. -func (c *Cluster) GetNonCoordinators() []*Command { +func (c *Cluster) GetNonPrimaries() []*Command { rtn := make([]*Command, 0) for _, n := range c.Nodes { if !n.IsPrimary() { @@ -166,6 +160,24 @@ func (c *Cluster) GetNonCoordinators() []*Command { return rtn } +// GetCoordinator gets the node which has been determined to be the coordinator. +// This used to be node0 in tests, but since implementing etcd, the coordinator +// can be any node in the cluster, so we have to use this method in tests which +// need to act on the coordinator. +func (c *Cluster) GetCoordinator() *Command { + return c.GetPrimary() +} + +// GetNonCoordinator gets first first non-coordinator node in the list of nodes. +func (c *Cluster) GetNonCoordinator() *Command { + return c.GetNonPrimary() +} + +// GetNonCoordinators gets all nodes except the coordinator. +func (c *Cluster) GetNonCoordinators() []*Command { + return c.GetNonPrimaries() +} + // nodePlace represents a node's ID and its index into the c.Nodes slice. type nodePlace struct { id string