Fill out the tests around coordinate/node functionality

This commit is contained in:
Kyle Havlovitz 2017-10-31 15:08:14 -07:00
parent fd4d9f1c16
commit 9909b661ac
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
7 changed files with 90 additions and 48 deletions

View File

@ -139,9 +139,6 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
return err return err
} }
if rule != nil && c.srv.config.ACLEnforceVersion8 { if rule != nil && c.srv.config.ACLEnforceVersion8 {
// We don't enforce the sentinel policy here, since at this time
// sentinel only applies to creating or updating node or service
// info, not updating coordinates.
if !rule.NodeWrite(args.Node, nil) { if !rule.NodeWrite(args.Node, nil) {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -213,10 +210,7 @@ func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.Inde
return err return err
} }
if rule != nil && c.srv.config.ACLEnforceVersion8 { if rule != nil && c.srv.config.ACLEnforceVersion8 {
// We don't enforce the sentinel policy here, since at this time if !rule.NodeRead(args.Node) {
// sentinel only applies to creating or updating node or service
// info, not updating coordinates.
if !rule.NodeWrite(args.Node, nil) {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
} }

View File

@ -555,27 +555,44 @@ func TestCoordinate_Node_ACLDeny(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Send an update for the first node. This should go through since we coord := generateRandomCoordinate()
// don't have version 8 ACLs enforced yet.
req := structs.CoordinateUpdateRequest{ req := structs.CoordinateUpdateRequest{
Datacenter: "dc1", Datacenter: "dc1",
Node: "node1", Node: "node1",
Coord: generateRandomCoordinate(), Coord: coord,
} }
var out struct{} var out struct{}
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil { if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// Try a read for the first node. This should go through since we
// don't have version 8 ACLs enforced yet.
arg := structs.NodeSpecificRequest{
Node: "node1",
Datacenter: "dc1",
}
resp := structs.IndexedCoordinates{}
retry.Run(t, func(r *retry.R) {
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp); err != nil {
r.Fatalf("err: %v", err)
}
if len(resp.Coordinates) != 1 ||
resp.Coordinates[0].Node != "node1" {
r.Fatalf("bad: %v", resp.Coordinates)
}
verify.Values(t, "", resp.Coordinates[0].Coord, coord)
})
// Now turn on version 8 enforcement and try again. // Now turn on version 8 enforcement and try again.
s1.config.ACLEnforceVersion8 = true s1.config.ACLEnforceVersion8 = true
err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out) err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp)
if !acl.IsErrPermissionDenied(err) { if !acl.IsErrPermissionDenied(err) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// Create an ACL that can write to the node. // Create an ACL that can read from the node.
arg := structs.ACLRequest{ aclReq := structs.ACLRequest{
Datacenter: "dc1", Datacenter: "dc1",
Op: structs.ACLSet, Op: structs.ACLSet,
ACL: structs.ACL{ ACL: structs.ACL{
@ -583,26 +600,26 @@ func TestCoordinate_Node_ACLDeny(t *testing.T) {
Type: structs.ACLTypeClient, Type: structs.ACLTypeClient,
Rules: ` Rules: `
node "node1" { node "node1" {
policy = "write" policy = "read"
} }
`, `,
}, },
WriteRequest: structs.WriteRequest{Token: "root"}, WriteRequest: structs.WriteRequest{Token: "root"},
} }
var id string var id string
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &id); err != nil { if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &aclReq, &id); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// With the token, it should now go through. // With the token, it should now go through.
req.Token = id arg.Token = id
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil { if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// But it should be blocked for the other node. // But it should be blocked for the other node.
req.Node = "node2" arg.Node = "node2"
err = msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out) err = msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp)
if !acl.IsErrPermissionDenied(err) { if !acl.IsErrPermissionDenied(err) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }

View File

@ -42,7 +42,8 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
} }
verify.Values(t, "", all, structs.Coordinates{}) verify.Values(t, "", all, structs.Coordinates{})
_, coords, err := s.Coordinate("nope", nil) coordinateWs := memdb.NewWatchSet()
_, coords, err := s.Coordinate("nope", coordinateWs)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -63,7 +64,7 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
if err := s.CoordinateBatchUpdate(1, updates); err != nil { if err := s.CoordinateBatchUpdate(1, updates); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if watchFired(ws) { if watchFired(ws) || watchFired(coordinateWs) {
t.Fatalf("bad") t.Fatalf("bad")
} }
@ -79,13 +80,22 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
} }
verify.Values(t, "", all, structs.Coordinates{}) verify.Values(t, "", all, structs.Coordinates{})
coordinateWs = memdb.NewWatchSet()
idx, coords, err = s.Coordinate("node1", coordinateWs)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 1 {
t.Fatalf("bad index: %d", idx)
}
// Register the nodes then do the update again. // Register the nodes then do the update again.
testRegisterNode(t, s, 1, "node1") testRegisterNode(t, s, 1, "node1")
testRegisterNode(t, s, 2, "node2") testRegisterNode(t, s, 2, "node2")
if err := s.CoordinateBatchUpdate(3, updates); err != nil { if err := s.CoordinateBatchUpdate(3, updates); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !watchFired(ws) { if !watchFired(ws) || !watchFired(coordinateWs) {
t.Fatalf("bad") t.Fatalf("bad")
} }
@ -101,11 +111,16 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
verify.Values(t, "", all, updates) verify.Values(t, "", all, updates)
// Also verify the per-node coordinate interface. // Also verify the per-node coordinate interface.
for _, update := range updates { nodeWs := make([]memdb.WatchSet, len(updates))
_, coords, err := s.Coordinate(update.Node, nil) for i, update := range updates {
nodeWs[i] = memdb.NewWatchSet()
idx, coords, err := s.Coordinate(update.Node, nodeWs[i])
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if idx != 3 {
t.Fatalf("bad index: %d", idx)
}
expected := lib.CoordinateSet{ expected := lib.CoordinateSet{
"": update.Coord, "": update.Coord,
} }
@ -120,6 +135,11 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
if !watchFired(ws) { if !watchFired(ws) {
t.Fatalf("bad") t.Fatalf("bad")
} }
for _, ws := range nodeWs {
if !watchFired(ws) {
t.Fatalf("bad")
}
}
// Verify it got applied. // Verify it got applied.
idx, all, err = s.Coordinates(nil) idx, all, err = s.Coordinates(nil)
@ -133,10 +153,13 @@ func TestStateStore_Coordinate_Updates(t *testing.T) {
// And check the per-node coordinate version of the same thing. // And check the per-node coordinate version of the same thing.
for _, update := range updates { for _, update := range updates {
_, coords, err := s.Coordinate(update.Node, nil) idx, coords, err := s.Coordinate(update.Node, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if idx != 4 {
t.Fatalf("bad index: %d", idx)
}
expected := lib.CoordinateSet{ expected := lib.CoordinateSet{
"": update.Coord, "": update.Coord,
} }

View File

@ -86,7 +86,7 @@ func (s *HTTPServer) CoordinateNodes(resp http.ResponseWriter, req *http.Request
return nil, err return nil, err
} }
return filterCoordinates(req, "", out.Coordinates), nil return filterCoordinates(req, out.Coordinates), nil
} }
// CoordinateNode returns the LAN node in the given datacenter, along with // CoordinateNode returns the LAN node in the given datacenter, along with
@ -109,10 +109,16 @@ func (s *HTTPServer) CoordinateNode(resp http.ResponseWriter, req *http.Request)
return nil, err return nil, err
} }
return filterCoordinates(req, node, out.Coordinates), nil result := filterCoordinates(req, out.Coordinates)
if len(result) == 0 {
resp.WriteHeader(http.StatusNotFound)
return nil, nil
}
return result, nil
} }
func filterCoordinates(req *http.Request, node string, in structs.Coordinates) structs.Coordinates { func filterCoordinates(req *http.Request, in structs.Coordinates) structs.Coordinates {
out := structs.Coordinates{} out := structs.Coordinates{}
if in == nil { if in == nil {
@ -126,9 +132,6 @@ func filterCoordinates(req *http.Request, node string, in structs.Coordinates) s
} }
for _, c := range in { for _, c := range in {
if node != "" && c.Node != node {
continue
}
if filterBySegment && c.Segment != segment { if filterBySegment && c.Segment != segment {
continue continue
} }

View File

@ -146,17 +146,15 @@ func TestCoordinate_Node(t *testing.T) {
a := NewTestAgent(t.Name(), "") a := NewTestAgent(t.Name(), "")
defer a.Shutdown() defer a.Shutdown()
// Make sure an empty list is non-nil. // Make sure we get a 404 with no coordinates.
req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil) req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
obj, err := a.srv.CoordinateNode(resp, req) obj, err := a.srv.CoordinateNode(resp, req)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if resp.Code != http.StatusNotFound {
coordinates := obj.(structs.Coordinates) t.Fatalf("bad: %v", resp.Code)
if coordinates == nil || len(coordinates) != 0 {
t.Fatalf("bad: %v", coordinates)
} }
// Register the nodes. // Register the nodes.
@ -204,7 +202,7 @@ func TestCoordinate_Node(t *testing.T) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
coordinates = obj.(structs.Coordinates) coordinates := obj.(structs.Coordinates)
if len(coordinates) != 1 || if len(coordinates) != 1 ||
coordinates[0].Node != "foo" { coordinates[0].Node != "foo" {
t.Fatalf("bad: %v", coordinates) t.Fatalf("bad: %v", coordinates)
@ -217,10 +215,8 @@ func TestCoordinate_Node(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if resp.Code != http.StatusNotFound {
coordinates = obj.(structs.Coordinates) t.Fatalf("bad: %v", resp.Code)
if len(coordinates) != 0 {
t.Fatalf("bad: %v", coordinates)
} }
// Filter on a real node segment // Filter on a real node segment
@ -243,9 +239,7 @@ func TestCoordinate_Node(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if resp.Code != http.StatusNotFound {
coordinates = obj.(structs.Coordinates) t.Fatalf("bad: %v", resp.Code)
if len(coordinates) != 0 {
t.Fatalf("bad: %v", coordinates)
} }
} }

View File

@ -3,6 +3,8 @@ package api
import ( import (
"testing" "testing"
"strings"
"github.com/hashicorp/consul/testutil/retry" "github.com/hashicorp/consul/testutil/retry"
) )
@ -51,7 +53,7 @@ func TestAPI_CoordinateNode(t *testing.T) {
coordinate := c.Coordinate() coordinate := c.Coordinate()
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
_, _, err := coordinate.Node(s.Config.NodeName, nil) _, _, err := coordinate.Node(s.Config.NodeName, nil)
if err != nil { if err != nil && !strings.Contains(err.Error(), "Unexpected response code: 404") {
r.Fatal(err) r.Fatal(err)
} }

View File

@ -94,6 +94,11 @@ The table below shows this endpoint's support for
- `dc` `(string: "")` - Specifies the datacenter to query. This will default to - `dc` `(string: "")` - Specifies the datacenter to query. This will default to
the datacenter of the agent being queried. This is specified as part of the the datacenter of the agent being queried. This is specified as part of the
URL as a query parameter. URL as a query parameter.
- `segment` `(string: "")` - (Enterprise-only) Specifies the segment to list members for.
If left blank, this will query for the default segment when connecting to a server and
the agent's own segment when connecting to a client (clients can only be part of one
network segment). When querying a server, setting this to the special string `_all`
will show members in all segments.
### Sample Request ### Sample Request
@ -125,8 +130,7 @@ segment.
## Read LAN Coordinates for a node ## Read LAN Coordinates for a node
This endpoint returns the LAN network coordinates for all nodes in a given This endpoint returns the LAN network coordinates for the given node.
datacenter.
| Method | Path | Produces | | Method | Path | Produces |
| ------ | ---------------------------- | -------------------------- | | ------ | ---------------------------- | -------------------------- |
@ -146,6 +150,11 @@ The table below shows this endpoint's support for
- `dc` `(string: "")` - Specifies the datacenter to query. This will default to - `dc` `(string: "")` - Specifies the datacenter to query. This will default to
the datacenter of the agent being queried. This is specified as part of the the datacenter of the agent being queried. This is specified as part of the
URL as a query parameter. URL as a query parameter.
- `segment` `(string: "")` - (Enterprise-only) Specifies the segment to list members for.
If left blank, this will query for the default segment when connecting to a server and
the agent's own segment when connecting to a client (clients can only be part of one
network segment). When querying a server, setting this to the special string `_all`
will show members in all segments.
### Sample Request ### Sample Request