txn: add tests for RPC endpoint
This commit is contained in:
parent
9f4f673c4d
commit
2408f99cca
|
@ -1354,6 +1354,11 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
|
||||||
|
|
||||||
// vetNodeTxnOp applies the given ACL policy to a node transaction operation.
|
// vetNodeTxnOp applies the given ACL policy to a node transaction operation.
|
||||||
func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
|
func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
|
||||||
|
// Fast path if ACLs are not enabled.
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
node := op.Node
|
node := op.Node
|
||||||
|
|
||||||
// Filtering for GETs is done on the output side.
|
// Filtering for GETs is done on the output side.
|
||||||
|
@ -1378,7 +1383,7 @@ func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rule.NodeWrite(node.Node, scope) {
|
if rule != nil && !rule.NodeWrite(node.Node, scope) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1387,6 +1392,11 @@ func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
|
||||||
|
|
||||||
// vetServiceTxnOp applies the given ACL policy to a service transaction operation.
|
// vetServiceTxnOp applies the given ACL policy to a service transaction operation.
|
||||||
func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error {
|
func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error {
|
||||||
|
// Fast path if ACLs are not enabled.
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
service := op.Service
|
service := op.Service
|
||||||
|
|
||||||
// Filtering for GETs is done on the output side.
|
// Filtering for GETs is done on the output side.
|
||||||
|
@ -1416,6 +1426,11 @@ func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error {
|
||||||
|
|
||||||
// vetCheckTxnOp applies the given ACL policy to a check transaction operation.
|
// vetCheckTxnOp applies the given ACL policy to a check transaction operation.
|
||||||
func vetCheckTxnOp(op *structs.TxnCheckOp, rule acl.Authorizer) error {
|
func vetCheckTxnOp(op *structs.TxnCheckOp, rule acl.Authorizer) error {
|
||||||
|
// Fast path if ACLs are not enabled.
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filtering for GETs is done on the output side.
|
// Filtering for GETs is done on the output side.
|
||||||
if op.Verb == api.CheckGet {
|
if op.Verb == api.CheckGet {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -132,16 +132,19 @@ func (s *Store) txnNode(tx *memdb.Txn, idx uint64, op *structs.TxnNodeOp) (struc
|
||||||
entry, err = getNodeIDTxn(tx, op.Node.ID)
|
entry, err = getNodeIDTxn(tx, op.Node.ID)
|
||||||
|
|
||||||
case api.NodeSet:
|
case api.NodeSet:
|
||||||
entry = &op.Node
|
|
||||||
err = s.ensureNodeTxn(tx, idx, &op.Node)
|
err = s.ensureNodeTxn(tx, idx, &op.Node)
|
||||||
|
if err == nil {
|
||||||
|
entry, err = getNodeIDTxn(tx, op.Node.ID)
|
||||||
|
}
|
||||||
|
|
||||||
case api.NodeCAS:
|
case api.NodeCAS:
|
||||||
var ok bool
|
var ok bool
|
||||||
entry = &op.Node
|
|
||||||
ok, err = s.ensureNodeCASTxn(tx, idx, &op.Node)
|
ok, err = s.ensureNodeCASTxn(tx, idx, &op.Node)
|
||||||
if !ok && err == nil {
|
if !ok && err == nil {
|
||||||
err = fmt.Errorf("failed to set node %q, index is stale", op.Node.Node)
|
err = fmt.Errorf("failed to set node %q, index is stale", op.Node.Node)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
entry, err = getNodeIDTxn(tx, op.Node.ID)
|
||||||
|
|
||||||
case api.NodeDelete:
|
case api.NodeDelete:
|
||||||
err = s.deleteNodeTxn(tx, idx, op.Node.Node)
|
err = s.deleteNodeTxn(tx, idx, op.Node.Node)
|
||||||
|
@ -187,8 +190,8 @@ func (s *Store) txnService(tx *memdb.Txn, idx uint64, op *structs.TxnServiceOp)
|
||||||
entry, err = s.nodeServiceTxn(tx, op.Node, op.Service.ID)
|
entry, err = s.nodeServiceTxn(tx, op.Node, op.Service.ID)
|
||||||
|
|
||||||
case api.ServiceSet:
|
case api.ServiceSet:
|
||||||
entry = &op.Service
|
|
||||||
err = s.ensureServiceTxn(tx, idx, op.Node, &op.Service)
|
err = s.ensureServiceTxn(tx, idx, op.Node, &op.Service)
|
||||||
|
entry, err = s.nodeServiceTxn(tx, op.Node, op.Service.ID)
|
||||||
|
|
||||||
case api.ServiceCAS:
|
case api.ServiceCAS:
|
||||||
var ok bool
|
var ok bool
|
||||||
|
@ -246,8 +249,10 @@ func (s *Store) txnCheck(tx *memdb.Txn, idx uint64, op *structs.TxnCheckOp) (str
|
||||||
}
|
}
|
||||||
|
|
||||||
case api.CheckSet:
|
case api.CheckSet:
|
||||||
entry = &op.Check
|
err = s.ensureCheckTxn(tx, idx, &op.Check)
|
||||||
err = s.ensureCheckTxn(tx, idx, entry)
|
if err == nil {
|
||||||
|
_, entry, err = s.nodeCheckTxn(tx, op.Check.Node, op.Check.CheckID)
|
||||||
|
}
|
||||||
|
|
||||||
case api.CheckCAS:
|
case api.CheckCAS:
|
||||||
var ok bool
|
var ok bool
|
||||||
|
@ -255,7 +260,9 @@ func (s *Store) txnCheck(tx *memdb.Txn, idx uint64, op *structs.TxnCheckOp) (str
|
||||||
ok, err = s.ensureCheckCASTxn(tx, idx, entry)
|
ok, err = s.ensureCheckCASTxn(tx, idx, entry)
|
||||||
if !ok && err == nil {
|
if !ok && err == nil {
|
||||||
err = fmt.Errorf("failed to set check %q on node %q, index is stale", entry.CheckID, entry.Node)
|
err = fmt.Errorf("failed to set check %q on node %q, index is stale", entry.CheckID, entry.Node)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
_, entry, err = s.nodeCheckTxn(tx, op.Check.Node, op.Check.CheckID)
|
||||||
|
|
||||||
case api.CheckDelete:
|
case api.CheckDelete:
|
||||||
err = s.deleteCheckTxn(tx, idx, op.Check.Node, op.Check.CheckID)
|
err = s.deleteCheckTxn(tx, idx, op.Check.Node, op.Check.CheckID)
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (t *Txn) preCheck(authorizer acl.Authorizer, ops structs.TxnOps) structs.Tx
|
||||||
}
|
}
|
||||||
case op.Service != nil:
|
case op.Service != nil:
|
||||||
service := &op.Service.Service
|
service := &op.Service.Service
|
||||||
if err := servicePreApply(service, authorizer); err != nil {
|
if err := servicePreApply(service, nil); err != nil {
|
||||||
errors = append(errors, &structs.TxnError{
|
errors = append(errors, &structs.TxnError{
|
||||||
OpIndex: i,
|
OpIndex: i,
|
||||||
What: err.Error(),
|
What: err.Error(),
|
||||||
|
|
|
@ -12,9 +12,49 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
|
"github.com/hashicorp/consul/types"
|
||||||
"github.com/hashicorp/net-rpc-msgpackrpc"
|
"github.com/hashicorp/net-rpc-msgpackrpc"
|
||||||
|
"github.com/pascaldekloe/goe/verify"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testTxnRules = `
|
||||||
|
key "" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
key "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
key "test" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
key "test/priv" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
|
||||||
|
service "" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
service "foo-svc" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "test-svc" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
|
||||||
|
node "" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
node "foo-node" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node "test-node" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var testNodeID = "9749a7df-fac5-46b4-8078-32a3d96c59f3"
|
||||||
|
|
||||||
func TestTxn_CheckNotExists(t *testing.T) {
|
func TestTxn_CheckNotExists(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
|
@ -101,12 +141,76 @@ func TestTxn_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeSet,
|
||||||
|
Node: structs.Node{
|
||||||
|
ID: types.NodeID(testNodeID),
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeGet,
|
||||||
|
Node: structs.Node{
|
||||||
|
ID: types.NodeID(testNodeID),
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceSet,
|
||||||
|
Node: "foo",
|
||||||
|
Service: structs.NodeService{
|
||||||
|
ID: "svc-foo",
|
||||||
|
Service: "svc-foo",
|
||||||
|
Address: "1.1.1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceGet,
|
||||||
|
Node: "foo",
|
||||||
|
Service: structs.NodeService{
|
||||||
|
ID: "svc-foo",
|
||||||
|
Service: "svc-foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckSet,
|
||||||
|
Check: structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: types.CheckID("check-foo"),
|
||||||
|
Name: "test",
|
||||||
|
Status: "passing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckGet,
|
||||||
|
Check: structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: types.CheckID("check-foo"),
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var out structs.TxnResponse
|
var out structs.TxnResponse
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "Txn.Apply", &arg, &out); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "Txn.Apply", &arg, &out); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(out.Errors) != 0 {
|
||||||
|
t.Fatalf("errs: %v", out.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the state store directly.
|
// Verify the state store directly.
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
|
@ -122,6 +226,30 @@ func TestTxn_Apply(t *testing.T) {
|
||||||
t.Fatalf("bad: %v", d)
|
t.Fatalf("bad: %v", d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, n, err := state.GetNode("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if n.Node != "foo" || n.Address != "127.0.0.1" {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, s, err := state.NodeService("foo", "svc-foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if s.ID != "svc-foo" || s.Address != "1.1.1.1" {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, c, err := state.NodeCheck("foo", types.CheckID("check-foo"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if c.CheckID != "check-foo" || c.Status != "passing" || c.Name != "test" {
|
||||||
|
t.Fatalf("bad: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the transaction's return value.
|
// Verify the transaction's return value.
|
||||||
expected := structs.TxnResponse{
|
expected := structs.TxnResponse{
|
||||||
Results: structs.TxnResults{
|
Results: structs.TxnResults{
|
||||||
|
@ -147,15 +275,34 @@ func TestTxn_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Node: n,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Node: n,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Service: s,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Service: s,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Check: c,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Check: c,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(out, expected) {
|
verify.Values(t, "", out, expected)
|
||||||
t.Fatalf("bad %v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTxn_Apply_ACLDeny(t *testing.T) {
|
func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
|
@ -167,15 +314,25 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
||||||
|
|
||||||
// Put in a key to read back.
|
// Set up some state to read back.
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
d := &structs.DirEntry{
|
d := &structs.DirEntry{
|
||||||
Key: "nope",
|
Key: "nope",
|
||||||
Value: []byte("hello"),
|
Value: []byte("hello"),
|
||||||
}
|
}
|
||||||
if err := state.KVSSet(1, d); err != nil {
|
require.NoError(state.KVSSet(1, d))
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
|
node := &structs.Node{
|
||||||
|
ID: types.NodeID(testNodeID),
|
||||||
|
Node: "nope",
|
||||||
}
|
}
|
||||||
|
require.NoError(state.EnsureNode(2, node))
|
||||||
|
|
||||||
|
svc := structs.NodeService{ID: "nope", Service: "nope", Address: "127.0.0.1"}
|
||||||
|
require.NoError(state.EnsureService(3, "nope", &svc))
|
||||||
|
|
||||||
|
check := structs.HealthCheck{Node: "nope", CheckID: types.CheckID("nope")}
|
||||||
|
state.EnsureCheck(4, &check)
|
||||||
|
|
||||||
// Create the ACL.
|
// Create the ACL.
|
||||||
var id string
|
var id string
|
||||||
|
@ -186,7 +343,7 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
ACL: structs.ACL{
|
ACL: structs.ACL{
|
||||||
Name: "User token",
|
Name: "User token",
|
||||||
Type: structs.ACLTokenTypeClient,
|
Type: structs.ACLTokenTypeClient,
|
||||||
Rules: testListRules,
|
Rules: testTxnRules,
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: "root"},
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
}
|
}
|
||||||
|
@ -296,6 +453,101 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeGet,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeSet,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeCAS,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeDelete,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeDeleteCAS,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceGet,
|
||||||
|
Node: "foo-node",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceSet,
|
||||||
|
Node: "foo-node",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceCAS,
|
||||||
|
Node: "foo-node",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceDelete,
|
||||||
|
Node: "foo-node",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceDeleteCAS,
|
||||||
|
Node: "foo-node",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckGet,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckSet,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckCAS,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckDelete,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckDeleteCAS,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{
|
WriteRequest: structs.WriteRequest{
|
||||||
Token: id,
|
Token: id,
|
||||||
|
@ -309,20 +561,55 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
// Verify the transaction's return value.
|
// Verify the transaction's return value.
|
||||||
var expected structs.TxnResponse
|
var expected structs.TxnResponse
|
||||||
for i, op := range arg.Ops {
|
for i, op := range arg.Ops {
|
||||||
switch op.KV.Verb {
|
switch {
|
||||||
case api.KVGet, api.KVGetTree:
|
case op.KV != nil:
|
||||||
// These get filtered but won't result in an error.
|
switch op.KV.Verb {
|
||||||
|
case api.KVGet, api.KVGetTree:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
expected.Errors = append(expected.Errors, &structs.TxnError{
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
OpIndex: i,
|
OpIndex: i,
|
||||||
What: acl.ErrPermissionDenied.Error(),
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
case op.Node != nil:
|
||||||
|
switch op.Node.Verb {
|
||||||
|
case api.NodeGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case op.Service != nil:
|
||||||
|
switch op.Service.Verb {
|
||||||
|
case api.ServiceGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case op.Check != nil:
|
||||||
|
switch op.Check.Verb {
|
||||||
|
case api.CheckGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(out, expected) {
|
|
||||||
t.Fatalf("bad %v", out)
|
verify.Values(t, "", out, expected)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTxn_Apply_LockDelay(t *testing.T) {
|
func TestTxn_Apply_LockDelay(t *testing.T) {
|
||||||
|
@ -413,6 +700,9 @@ func TestTxn_Apply_LockDelay(t *testing.T) {
|
||||||
|
|
||||||
func TestTxn_Read(t *testing.T) {
|
func TestTxn_Read(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -431,6 +721,19 @@ func TestTxn_Read(t *testing.T) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Put in a node/check/service to read back.
|
||||||
|
node := &structs.Node{
|
||||||
|
ID: types.NodeID(testNodeID),
|
||||||
|
Node: "foo",
|
||||||
|
}
|
||||||
|
require.NoError(state.EnsureNode(2, node))
|
||||||
|
|
||||||
|
svc := structs.NodeService{ID: "svc-foo", Service: "svc-foo", Address: "127.0.0.1"}
|
||||||
|
require.NoError(state.EnsureService(3, "foo", &svc))
|
||||||
|
|
||||||
|
check := structs.HealthCheck{Node: "foo", CheckID: types.CheckID("check-foo")}
|
||||||
|
state.EnsureCheck(4, &check)
|
||||||
|
|
||||||
// Do a super basic request. The state store test covers the details so
|
// Do a super basic request. The state store test covers the details so
|
||||||
// we just need to be sure that the transaction is sent correctly and
|
// we just need to be sure that the transaction is sent correctly and
|
||||||
// the results are converted appropriately.
|
// the results are converted appropriately.
|
||||||
|
@ -445,6 +748,25 @@ func TestTxn_Read(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeGet,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceGet,
|
||||||
|
Node: "foo",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckGet,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var out structs.TxnReadResponse
|
var out structs.TxnReadResponse
|
||||||
|
@ -453,6 +775,8 @@ func TestTxn_Read(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the transaction's return value.
|
// Verify the transaction's return value.
|
||||||
|
svc.Weights = &structs.Weights{Passing: 1, Warning: 1}
|
||||||
|
svc.RaftIndex = structs.RaftIndex{CreateIndex: 3, ModifyIndex: 3}
|
||||||
expected := structs.TxnReadResponse{
|
expected := structs.TxnReadResponse{
|
||||||
TxnResponse: structs.TxnResponse{
|
TxnResponse: structs.TxnResponse{
|
||||||
Results: structs.TxnResults{
|
Results: structs.TxnResults{
|
||||||
|
@ -466,19 +790,29 @@ func TestTxn_Read(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Node: node,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Service: &svc,
|
||||||
|
},
|
||||||
|
&structs.TxnResult{
|
||||||
|
Check: &check,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
QueryMeta: structs.QueryMeta{
|
QueryMeta: structs.QueryMeta{
|
||||||
KnownLeader: true,
|
KnownLeader: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(out, expected) {
|
verify.Values(t, "", out, expected)
|
||||||
t.Fatalf("bad %v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTxn_Read_ACLDeny(t *testing.T) {
|
func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.ACLDatacenter = "dc1"
|
c.ACLDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
|
@ -502,6 +836,19 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Put in a node/check/service to read back.
|
||||||
|
node := &structs.Node{
|
||||||
|
ID: types.NodeID(testNodeID),
|
||||||
|
Node: "nope",
|
||||||
|
}
|
||||||
|
require.NoError(state.EnsureNode(2, node))
|
||||||
|
|
||||||
|
svc := structs.NodeService{ID: "nope", Service: "nope", Address: "127.0.0.1"}
|
||||||
|
require.NoError(state.EnsureService(3, "nope", &svc))
|
||||||
|
|
||||||
|
check := structs.HealthCheck{Node: "nope", CheckID: types.CheckID("nope")}
|
||||||
|
state.EnsureCheck(4, &check)
|
||||||
|
|
||||||
// Create the ACL.
|
// Create the ACL.
|
||||||
var id string
|
var id string
|
||||||
{
|
{
|
||||||
|
@ -511,7 +858,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
ACL: structs.ACL{
|
ACL: structs.ACL{
|
||||||
Name: "User token",
|
Name: "User token",
|
||||||
Type: structs.ACLTokenTypeClient,
|
Type: structs.ACLTokenTypeClient,
|
||||||
Rules: testListRules,
|
Rules: testTxnRules,
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: "root"},
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
||||||
}
|
}
|
||||||
|
@ -557,6 +904,25 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Node: &structs.TxnNodeOp{
|
||||||
|
Verb: api.NodeGet,
|
||||||
|
Node: structs.Node{ID: node.ID, Node: node.Node},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Service: &structs.TxnServiceOp{
|
||||||
|
Verb: api.ServiceGet,
|
||||||
|
Node: "foo",
|
||||||
|
Service: svc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.TxnOp{
|
||||||
|
Check: &structs.TxnCheckOp{
|
||||||
|
Verb: api.CheckGet,
|
||||||
|
Check: check,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
QueryOptions: structs.QueryOptions{
|
QueryOptions: structs.QueryOptions{
|
||||||
Token: id,
|
Token: id,
|
||||||
|
@ -574,15 +940,51 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, op := range arg.Ops {
|
for i, op := range arg.Ops {
|
||||||
switch op.KV.Verb {
|
switch {
|
||||||
case api.KVGet, api.KVGetTree:
|
case op.KV != nil:
|
||||||
// These get filtered but won't result in an error.
|
switch op.KV.Verb {
|
||||||
|
case api.KVGet, api.KVGetTree:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
default:
|
default:
|
||||||
expected.Errors = append(expected.Errors, &structs.TxnError{
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
OpIndex: i,
|
OpIndex: i,
|
||||||
What: acl.ErrPermissionDenied.Error(),
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
case op.Node != nil:
|
||||||
|
switch op.Node.Verb {
|
||||||
|
case api.NodeGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case op.Service != nil:
|
||||||
|
switch op.Service.Verb {
|
||||||
|
case api.ServiceGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case op.Check != nil:
|
||||||
|
switch op.Check.Verb {
|
||||||
|
case api.CheckGet:
|
||||||
|
// These get filtered but won't result in an error.
|
||||||
|
|
||||||
|
default:
|
||||||
|
expected.Errors = append(expected.Errors, &structs.TxnError{
|
||||||
|
OpIndex: i,
|
||||||
|
What: acl.ErrPermissionDenied.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(out, expected) {
|
if !reflect.DeepEqual(out, expected) {
|
||||||
|
|
Loading…
Reference in New Issue