2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2015-11-07 06:18:11 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
2015-11-11 05:16:04 +00:00
|
|
|
"bytes"
|
2022-07-22 13:14:43 +00:00
|
|
|
"context"
|
2023-04-24 20:21:28 +00:00
|
|
|
"errors"
|
2015-11-10 22:46:08 +00:00
|
|
|
"fmt"
|
2015-11-07 06:18:11 +00:00
|
|
|
"os"
|
2015-11-10 18:29:55 +00:00
|
|
|
"reflect"
|
2015-11-11 02:23:37 +00:00
|
|
|
"sort"
|
2015-11-10 07:03:20 +00:00
|
|
|
"strings"
|
2015-11-07 06:18:11 +00:00
|
|
|
"testing"
|
2015-11-11 01:42:52 +00:00
|
|
|
"time"
|
2015-11-07 06:18:11 +00:00
|
|
|
|
2021-08-06 22:00:58 +00:00
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/serf/coordinate"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2022-07-22 13:14:43 +00:00
|
|
|
"google.golang.org/grpc"
|
2021-08-06 22:00:58 +00:00
|
|
|
|
2022-04-05 21:10:06 +00:00
|
|
|
msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc"
|
|
|
|
"github.com/hashicorp/consul-net-rpc/net/rpc"
|
|
|
|
|
2017-08-23 14:52:48 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
2022-09-29 21:49:58 +00:00
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
2022-08-01 14:33:18 +00:00
|
|
|
grpcexternal "github.com/hashicorp/consul/agent/grpc-external"
|
2017-07-06 10:34:00 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2022-07-01 15:18:33 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs/aclfilter"
|
2019-11-25 17:07:04 +00:00
|
|
|
tokenStore "github.com/hashicorp/consul/agent/token"
|
2017-04-19 23:00:11 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2023-02-17 21:14:46 +00:00
|
|
|
"github.com/hashicorp/consul/proto/private/pbpeering"
|
2022-09-29 21:49:58 +00:00
|
|
|
"github.com/hashicorp/consul/sdk/freeport"
|
2019-03-27 12:54:56 +00:00
|
|
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
2019-04-25 16:26:33 +00:00
|
|
|
"github.com/hashicorp/consul/testrpc"
|
2018-04-10 12:28:27 +00:00
|
|
|
"github.com/hashicorp/consul/types"
|
2015-11-07 06:18:11 +00:00
|
|
|
)
|
|
|
|
|
2023-04-24 20:21:28 +00:00
|
|
|
const localTestDC = "dc1"
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
func TestPreparedQuery_Apply(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-07 06:18:11 +00:00
|
|
|
dir1, s1 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2017-04-19 23:00:11 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
2015-11-07 06:18:11 +00:00
|
|
|
|
2015-11-10 07:03:20 +00:00
|
|
|
// Set up a bare bones query.
|
2015-11-10 19:16:17 +00:00
|
|
|
query := structs.PreparedQueryRequest{
|
2015-11-07 06:18:11 +00:00
|
|
|
Datacenter: "dc1",
|
2015-11-10 04:37:41 +00:00
|
|
|
Op: structs.PreparedQueryCreate,
|
2015-11-10 18:29:55 +00:00
|
|
|
Query: &structs.PreparedQuery{
|
2016-12-10 19:19:08 +00:00
|
|
|
Name: "test",
|
2015-11-07 06:18:11 +00:00
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "redis",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply string
|
2015-11-10 07:03:20 +00:00
|
|
|
|
|
|
|
// Set an ID which should fail the create.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Query.ID = "nope"
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "ID must be empty") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change it to a bogus modify which should also fail.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.ID = generateUUID()
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Cannot modify non-existent prepared query") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix up the ID but invalidate the query itself. This proves we call
|
|
|
|
// parseQuery for a create, but that function is checked in detail as
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// part of another test so we don't have to exercise all the checks
|
|
|
|
// here.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryCreate
|
|
|
|
query.Query.ID = ""
|
|
|
|
query.Query.Service.Failover.NearestN = -1
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Bad NearestN") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-07-22 13:14:43 +00:00
|
|
|
// Fix that and ensure Targets and NearestN cannot be set at the same time.
|
|
|
|
query.Query.Service.Failover.NearestN = 1
|
2022-10-04 18:46:15 +00:00
|
|
|
query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{Peer: "peer"}}
|
2022-07-22 13:14:43 +00:00
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Targets cannot be populated with") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix that and ensure Targets and Datacenters cannot be set at the same time.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Query.Service.Failover.NearestN = 0
|
2022-07-22 13:14:43 +00:00
|
|
|
query.Query.Service.Failover.Datacenters = []string{"dc2"}
|
2022-10-04 18:46:15 +00:00
|
|
|
query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{Peer: "peer"}}
|
2022-07-22 13:14:43 +00:00
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Targets cannot be populated with") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix that and make sure it propagates an error from the Raft apply.
|
|
|
|
query.Query.Service.Failover.Targets = nil
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.Query.Session = "nope"
|
2015-11-10 19:16:17 +00:00
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2019-11-25 17:07:04 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "invalid session") {
|
2015-11-10 07:03:20 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix that and make sure the apply goes through.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.Query.Session = ""
|
2015-11-10 19:16:17 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
2015-11-10 07:03:20 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 18:29:55 +00:00
|
|
|
// Capture the ID and read the query back to verify.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Query.ID = reply
|
2015-11-10 18:29:55 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
2015-11-10 18:29:55 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 18:29:55 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
2015-11-10 19:16:17 +00:00
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
2015-11-10 18:29:55 +00:00
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the op an update. This should go through now that we have an ID.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.Service.Failover.NearestN = 2
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
2015-11-10 07:03:20 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 18:29:55 +00:00
|
|
|
// Read back again to verify the update worked.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
2015-11-10 18:29:55 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 18:29:55 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
2015-11-10 19:16:17 +00:00
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
2015-11-10 18:29:55 +00:00
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-10 07:03:20 +00:00
|
|
|
// Give a bogus op and make sure it fails.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = "nope"
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Unknown prepared query operation:") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prove that an update also goes through the parseQuery validation.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.Service.Failover.NearestN = -1
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2015-11-10 07:03:20 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Bad NearestN") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now change the op to delete; the bad query field should be ignored
|
|
|
|
// because all we care about for a delete op is the ID.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryDelete
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
2015-11-10 07:03:20 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 18:29:55 +00:00
|
|
|
// Verify that this query is deleted.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
if !structs.IsErrQueryNotFound(err) {
|
2015-11-13 20:57:06 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPreparedQuery_Apply_ACLDeny(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-10 19:16:17 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2015-11-10 19:16:17 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
2015-11-10 19:16:17 +00:00
|
|
|
|
2021-09-03 20:13:11 +00:00
|
|
|
rules := `
|
|
|
|
query_prefix "redis" {
|
|
|
|
policy = "write"
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
2021-09-03 20:13:11 +00:00
|
|
|
`
|
|
|
|
token := createToken(t, codec, rules)
|
2015-11-10 19:16:17 +00:00
|
|
|
|
|
|
|
// Set up a bare bones query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2022-01-20 12:47:50 +00:00
|
|
|
Name: "redis-primary",
|
2015-11-10 19:16:17 +00:00
|
|
|
Service: structs.ServiceQuery{
|
2016-02-24 09:26:16 +00:00
|
|
|
Service: "the-redis",
|
2015-11-10 19:16:17 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
|
|
|
|
// Creating without a token should fail since the default policy is to
|
|
|
|
// deny.
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now add the token and try again.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.WriteRequest.Token = token
|
2015-11-10 19:16:17 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Capture the ID and set the token, then read back the query to verify.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Note that unlike previous versions of Consul, we DO NOT capture the
|
|
|
|
// token. We will set that here just to be explicit about it.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Query.ID = reply
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.Query.Token = ""
|
2015-11-10 19:16:17 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Try to do an update without a token; this should get rejected.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.WriteRequest.Token = ""
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Try again with the original token; this should go through.
|
|
|
|
query.WriteRequest.Token = token
|
2015-11-10 19:16:17 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Try to do a delete with no token; this should get rejected.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Op = structs.PreparedQueryDelete
|
|
|
|
query.WriteRequest.Token = ""
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try again with the original token. This should go through.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.WriteRequest.Token = token
|
2015-11-10 19:16:17 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the query got deleted.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
if !structs.IsErrQueryNotFound(err) {
|
2015-11-13 20:57:06 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the query again.
|
|
|
|
query.Op = structs.PreparedQueryCreate
|
|
|
|
query.Query.ID = ""
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.WriteRequest.Token = token
|
2015-11-10 19:16:17 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Check that it's there, and again make sure that the token did not get
|
|
|
|
// captured.
|
2015-11-10 19:16:17 +00:00
|
|
|
query.Query.ID = reply
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.Query.Token = ""
|
2015-11-10 19:16:17 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A management token should be able to update the query no matter what.
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.WriteRequest.Token = "root"
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// That last update should not have captured a token.
|
|
|
|
query.Query.Token = ""
|
2015-11-10 19:16:17 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 19:16:17 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 19:16:17 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A management token should be able to delete the query no matter what.
|
|
|
|
query.Op = structs.PreparedQueryDelete
|
|
|
|
query.WriteRequest.Token = "root"
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the query got deleted.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 18:29:55 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
if !structs.IsErrQueryNotFound(err) {
|
2015-11-13 20:57:06 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 18:29:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
2015-11-10 07:03:20 +00:00
|
|
|
}
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// Use the root token to make a query under a different name.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.Op = structs.PreparedQueryCreate
|
|
|
|
query.Query.ID = ""
|
2016-02-24 09:26:16 +00:00
|
|
|
query.Query.Name = "cassandra"
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.WriteRequest.Token = "root"
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that it's there and that the token did not get captured.
|
|
|
|
query.Query.ID = reply
|
|
|
|
query.Query.Token = ""
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now try to change that to redis with the valid redis token. This will
|
|
|
|
// fail because that token can't change cassandra queries.
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
2016-02-24 09:26:16 +00:00
|
|
|
query.Query.Name = "redis"
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
query.WriteRequest.Token = token
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2015-11-07 06:18:11 +00:00
|
|
|
}
|
2015-11-10 19:33:00 +00:00
|
|
|
|
2015-11-10 22:46:08 +00:00
|
|
|
func TestPreparedQuery_Apply_ForwardLeader(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2016-10-26 02:20:24 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
|
|
c.Bootstrap = false
|
|
|
|
})
|
2015-11-10 22:46:08 +00:00
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec1 := rpcClient(t, s1)
|
|
|
|
defer codec1.Close()
|
|
|
|
|
|
|
|
dir2, s2 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir2)
|
|
|
|
defer s2.Shutdown()
|
|
|
|
codec2 := rpcClient(t, s2)
|
|
|
|
defer codec2.Close()
|
|
|
|
|
|
|
|
// Try to join.
|
2017-05-05 10:29:49 +00:00
|
|
|
joinLAN(t, s2, s1)
|
2015-11-10 22:46:08 +00:00
|
|
|
|
2017-04-19 23:00:11 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc1")
|
2015-11-10 22:46:08 +00:00
|
|
|
|
|
|
|
// Use the follower as the client.
|
|
|
|
var codec rpc.ClientCodec
|
|
|
|
if !s1.IsLeader() {
|
|
|
|
codec = codec1
|
|
|
|
} else {
|
|
|
|
codec = codec2
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a node and service in the catalog.
|
|
|
|
{
|
|
|
|
req := structs.RegisterRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "foo",
|
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
Service: "redis",
|
2022-01-20 12:47:50 +00:00
|
|
|
Tags: []string{"primary"},
|
2015-11-10 22:46:08 +00:00
|
|
|
Port: 8000,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply struct{}
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a bare bones query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2016-12-10 19:19:08 +00:00
|
|
|
Name: "test",
|
2015-11-10 22:46:08 +00:00
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "redis",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the apply works even when forwarded through the non-leader.
|
|
|
|
var reply string
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-10 19:33:00 +00:00
|
|
|
func TestPreparedQuery_parseQuery(t *testing.T) {
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-10 19:33:00 +00:00
|
|
|
query := &structs.PreparedQuery{}
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
err := parseQuery(query)
|
2016-12-10 19:19:08 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Must be bound to a session") {
|
2015-11-10 19:33:00 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
query.Session = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
2020-05-29 21:16:03 +00:00
|
|
|
err = parseQuery(query)
|
2016-12-10 19:19:08 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
2016-02-25 01:23:09 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
query.Session = ""
|
|
|
|
query.Template.Type = "some-kind-of-template"
|
2020-05-29 21:16:03 +00:00
|
|
|
err = parseQuery(query)
|
2016-12-10 19:19:08 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "Must provide a Service") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
2016-02-25 01:23:09 +00:00
|
|
|
}
|
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
query.Template.Type = ""
|
2020-05-29 21:16:03 +00:00
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Must be bound to a session") {
|
2015-11-10 19:33:00 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
// None of the rest of these care about version 8 ACL enforcement.
|
2020-05-29 21:16:03 +00:00
|
|
|
query = &structs.PreparedQuery{}
|
|
|
|
query.Session = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
|
|
|
query.Service.Service = "foo"
|
|
|
|
if err := parseQuery(query); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 19:33:00 +00:00
|
|
|
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Token = aclfilter.RedactedToken
|
2020-05-29 21:16:03 +00:00
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Bad Token") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2015-11-10 19:33:00 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.Token = "adf4238a-882b-9ddc-4a9d-5b6758e4159e"
|
|
|
|
if err := parseQuery(query); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 19:33:00 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.Service.Failover.NearestN = -1
|
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Bad NearestN") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2016-12-10 19:19:08 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.Service.Failover.NearestN = 3
|
|
|
|
if err := parseQuery(query); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2016-12-10 19:19:08 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.DNS.TTL = "two fortnights"
|
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Bad DNS TTL") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2016-12-10 19:19:08 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.DNS.TTL = "-3s"
|
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "must be >=0") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2016-12-10 19:19:08 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.DNS.TTL = "3s"
|
|
|
|
if err := parseQuery(query); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2017-01-23 23:53:45 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.Service.NodeMeta = map[string]string{"": "somevalue"}
|
|
|
|
err = parseQuery(query)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "cannot be blank") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
2017-01-23 23:53:45 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
query.Service.NodeMeta = map[string]string{"somekey": "somevalue"}
|
|
|
|
if err := parseQuery(query); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
2015-11-10 19:33:00 +00:00
|
|
|
}
|
|
|
|
}
|
2015-11-10 20:13:40 +00:00
|
|
|
|
2016-03-03 09:04:12 +00:00
|
|
|
func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2016-03-03 07:59:35 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2016-03-03 07:59:35 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
2016-03-03 07:59:35 +00:00
|
|
|
|
2021-09-03 20:13:11 +00:00
|
|
|
rules := `
|
|
|
|
query "" {
|
|
|
|
policy = "write"
|
2016-03-03 07:59:35 +00:00
|
|
|
}
|
2021-09-03 20:13:11 +00:00
|
|
|
`
|
|
|
|
token := createToken(t, codec, rules)
|
2016-03-03 07:59:35 +00:00
|
|
|
|
|
|
|
// Set up a catch-all template.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
|
|
|
Name: "",
|
|
|
|
Token: "5e1e24e5-1329-f86f-18c6-3d3734edb2cd",
|
|
|
|
Template: structs.QueryTemplateOptions{
|
|
|
|
Type: structs.QueryTemplateTypeNamePrefixMatch,
|
|
|
|
},
|
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "${name.full}",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
|
|
|
|
// Creating without a token should fail since the default policy is to
|
|
|
|
// deny.
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2016-03-03 07:59:35 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now add the token and try again.
|
|
|
|
query.WriteRequest.Token = token
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Capture the ID and read back the query to verify. Note that the token
|
|
|
|
// will be redacted since this isn't a management token.
|
|
|
|
query.Query.ID = reply
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Query.Token = aclfilter.RedactedToken
|
2016-03-03 07:59:35 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to query by ID without a token and make sure it gets denied, even
|
|
|
|
// though this has an empty name and would normally be shown.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2016-03-03 07:59:35 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should get the same result listing all the queries without a
|
|
|
|
// token.
|
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// But a management token should be able to see it, and the real token.
|
|
|
|
query.Query.Token = "5e1e24e5-1329-f86f-18c6-3d3734edb2cd"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
2016-03-03 09:04:12 +00:00
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Explaining should also be denied without a token.
|
2016-03-03 09:04:12 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "anything",
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2016-03-03 09:04:12 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// The user can explain and see the redacted token.
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Query.Token = aclfilter.RedactedToken
|
2016-03-03 09:04:12 +00:00
|
|
|
query.Query.Service.Service = "anything"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "anything",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2016-03-03 09:04:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := &resp.Query
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Make sure the management token can also explain and see the token.
|
2016-03-03 09:04:12 +00:00
|
|
|
query.Query.Token = "5e1e24e5-1329-f86f-18c6-3d3734edb2cd"
|
|
|
|
query.Query.Service.Service = "anything"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "anything",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2016-03-03 09:04:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := &resp.Query
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
2016-03-03 07:59:35 +00:00
|
|
|
}
|
|
|
|
|
2015-11-11 20:20:40 +00:00
|
|
|
func TestPreparedQuery_Get(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-10 20:13:40 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2015-11-10 20:13:40 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
2015-11-10 20:13:40 +00:00
|
|
|
|
2021-09-03 20:13:11 +00:00
|
|
|
rules := `
|
|
|
|
query_prefix "redis" {
|
|
|
|
policy = "write"
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
2021-09-03 20:13:11 +00:00
|
|
|
`
|
|
|
|
token := createToken(t, codec, rules)
|
2015-11-10 20:13:40 +00:00
|
|
|
|
|
|
|
// Set up a bare bones query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2022-01-20 12:47:50 +00:00
|
|
|
Name: "redis-primary",
|
2015-11-10 20:13:40 +00:00
|
|
|
Service: structs.ServiceQuery{
|
2016-02-24 09:26:16 +00:00
|
|
|
Service: "the-redis",
|
2015-11-10 20:13:40 +00:00
|
|
|
},
|
|
|
|
},
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
WriteRequest: structs.WriteRequest{Token: token},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Capture the ID, then read back the query to verify.
|
2015-11-10 20:13:40 +00:00
|
|
|
query.Query.ID = reply
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 20:13:40 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Try again with no token, which should return an error.
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: ""},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2015-11-10 20:13:40 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A management token should be able to read no matter what.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2015-11-10 20:13:40 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
// Create a session.
|
|
|
|
var session string
|
|
|
|
{
|
|
|
|
req := structs.SessionRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.SessionCreate,
|
|
|
|
Session: structs.Session{
|
|
|
|
Node: s1.config.NodeName,
|
|
|
|
},
|
2020-05-29 21:16:03 +00:00
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
2016-12-10 19:19:08 +00:00
|
|
|
}
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &req, &session); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// Now update the query to take away its name.
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.Name = ""
|
2016-12-10 19:19:08 +00:00
|
|
|
query.Query.Session = session
|
2016-02-24 09:26:16 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try again with no token, this should work since this query is only
|
|
|
|
// managed by an ID (no name) so no ACLs apply to it.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: ""},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
// Capture a token.
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.Token = "le-token"
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should get redacted when we read it back without a token.
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Query.Token = aclfilter.RedactedToken
|
2016-02-26 23:59:00 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: ""},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// But a management token should be able to see it.
|
|
|
|
query.Query.Token = "le-token"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: query.Query.ID,
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 20:20:40 +00:00
|
|
|
// Try to get an unknown ID.
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQuerySpecificRequest{
|
2015-11-11 20:20:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryID: generateUUID(),
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2015-11-11 20:20:40 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp); err != nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
if !structs.IsErrQueryNotFound(err) {
|
2015-11-13 20:57:06 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPreparedQuery_List(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-10 20:13:40 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2015-11-10 20:13:40 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
2015-11-10 20:13:40 +00:00
|
|
|
|
2021-09-03 20:13:11 +00:00
|
|
|
rules := `
|
|
|
|
query_prefix "redis" {
|
|
|
|
policy = "write"
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
2021-09-03 20:13:11 +00:00
|
|
|
`
|
|
|
|
token := createToken(t, codec, rules)
|
2015-11-10 20:13:40 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Query with a legit token but no queries.
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a bare bones query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2022-01-20 12:47:50 +00:00
|
|
|
Name: "redis-primary",
|
2016-02-26 23:59:00 +00:00
|
|
|
Token: "le-token",
|
2015-11-10 20:13:40 +00:00
|
|
|
Service: structs.ServiceQuery{
|
2016-02-24 09:26:16 +00:00
|
|
|
Service: "the-redis",
|
2015-11-10 20:13:40 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: token},
|
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
// Capture the ID and read back the query to verify. We also make sure
|
|
|
|
// the captured token gets redacted.
|
2015-11-10 20:13:40 +00:00
|
|
|
query.Query.ID = reply
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Query.Token = aclfilter.RedactedToken
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
if len(resp.Queries) != 1 {
|
2015-11-10 20:13:40 +00:00
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// An empty token should result in an empty list because of ACL
|
|
|
|
// filtering.
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: ""},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-03 23:04:09 +00:00
|
|
|
// Same for a token without access to the query.
|
|
|
|
{
|
|
|
|
token := createTokenWithPolicyName(t, codec, "deny-queries", `
|
|
|
|
query_prefix "" {
|
|
|
|
policy = "deny"
|
|
|
|
}
|
|
|
|
`, "root")
|
|
|
|
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
if !resp.QueryMeta.ResultsFilteredByACLs {
|
|
|
|
t.Fatal("ResultsFilteredByACLs should be true")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
// But a management token should work, and be able to see the captured
|
|
|
|
// token.
|
|
|
|
query.Query.Token = "le-token"
|
2015-11-10 20:13:40 +00:00
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
2016-02-24 09:26:16 +00:00
|
|
|
|
2016-12-10 19:19:08 +00:00
|
|
|
// Create a session.
|
|
|
|
var session string
|
|
|
|
{
|
|
|
|
req := structs.SessionRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.SessionCreate,
|
|
|
|
Session: structs.Session{
|
|
|
|
Node: s1.config.NodeName,
|
|
|
|
},
|
2020-05-29 21:16:03 +00:00
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
2016-12-10 19:19:08 +00:00
|
|
|
}
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &req, &session); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-24 09:26:16 +00:00
|
|
|
// Now take away the query name.
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
query.Query.Name = ""
|
2016-12-10 19:19:08 +00:00
|
|
|
query.Query.Session = session
|
2016-02-24 09:26:16 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// A query with the redis token shouldn't show anything since it doesn't
|
|
|
|
// match any un-named queries.
|
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 0 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// But a management token should work.
|
|
|
|
{
|
|
|
|
req := &structs.DCSpecificRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Queries) != 1 {
|
|
|
|
t.Fatalf("bad: %v", resp)
|
|
|
|
}
|
|
|
|
actual := resp.Queries[0]
|
|
|
|
if resp.Index != actual.ModifyIndex {
|
|
|
|
t.Fatalf("bad index: %d", resp.Index)
|
|
|
|
}
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
2015-11-10 20:13:40 +00:00
|
|
|
}
|
2015-11-10 23:16:41 +00:00
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
func TestPreparedQuery_Explain(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2016-03-03 09:04:12 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2016-03-03 09:04:12 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
2016-03-03 09:04:12 +00:00
|
|
|
|
2021-09-03 20:13:11 +00:00
|
|
|
rules := `
|
|
|
|
query_prefix "prod-" {
|
|
|
|
policy = "write"
|
2016-03-03 09:04:12 +00:00
|
|
|
}
|
2021-09-03 20:13:11 +00:00
|
|
|
`
|
|
|
|
token := createToken(t, codec, rules)
|
2016-03-03 09:04:12 +00:00
|
|
|
|
|
|
|
// Set up a template.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
|
|
|
Name: "prod-",
|
|
|
|
Token: "5e1e24e5-1329-f86f-18c6-3d3734edb2cd",
|
|
|
|
Template: structs.QueryTemplateOptions{
|
|
|
|
Type: structs.QueryTemplateTypeNamePrefixMatch,
|
|
|
|
},
|
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "${name.full}",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: token},
|
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Explain via the management token.
|
2016-03-03 09:04:12 +00:00
|
|
|
query.Query.ID = reply
|
|
|
|
query.Query.Service.Service = "prod-redis"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "prod-redis",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2016-03-03 09:04:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := &resp.Query
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Explain via the user token, which will redact the captured token.
|
2022-07-01 15:18:33 +00:00
|
|
|
query.Query.Token = aclfilter.RedactedToken
|
2016-03-03 09:04:12 +00:00
|
|
|
query.Query.Service.Service = "prod-redis"
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "prod-redis",
|
|
|
|
QueryOptions: structs.QueryOptions{Token: token},
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2016-03-03 09:04:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := &resp.Query
|
|
|
|
actual.CreateIndex, actual.ModifyIndex = 0, 0
|
|
|
|
if !reflect.DeepEqual(actual, query.Query) {
|
|
|
|
t.Fatalf("bad: %v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Explaining should be denied without a token, since the user isn't
|
2016-03-03 09:04:12 +00:00
|
|
|
// allowed to see the query.
|
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "prod-redis",
|
|
|
|
}
|
2016-03-04 01:30:36 +00:00
|
|
|
var resp structs.PreparedQueryExplainResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp)
|
2017-08-23 14:52:48 +00:00
|
|
|
if !acl.IsErrPermissionDenied(err) {
|
2016-03-03 09:04:12 +00:00
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-04 01:30:36 +00:00
|
|
|
// Try to explain a bogus ID.
|
2016-03-03 09:04:12 +00:00
|
|
|
{
|
|
|
|
req := &structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: generateUUID(),
|
|
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
|
|
}
|
|
|
|
var resp structs.IndexedPreparedQueries
|
2016-03-04 01:30:36 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Explain", req, &resp); err != nil {
|
2020-07-01 00:49:13 +00:00
|
|
|
if !structs.IsErrQueryNotFound(err) {
|
2016-03-03 09:04:12 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// This is a beast of a test, but the setup is so extensive it makes sense to
|
|
|
|
// walk through the different cases once we have it up. This is broken into
|
|
|
|
// sections so it's still pretty easy to read.
|
|
|
|
func TestPreparedQuery_Execute(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2023-03-27 22:40:49 +00:00
|
|
|
es := createExecuteServers(t)
|
2020-05-29 21:16:03 +00:00
|
|
|
|
|
|
|
newSessionDC1 := func(t *testing.T) string {
|
|
|
|
t.Helper()
|
|
|
|
req := structs.SessionRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.SessionCreate,
|
|
|
|
Session: structs.Session{
|
2023-03-27 22:40:49 +00:00
|
|
|
Node: es.server.server.config.NodeName,
|
2020-05-29 21:16:03 +00:00
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
|
|
|
var session string
|
2023-03-27 22:40:49 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(es.server.codec, "Session.Apply", &req, &session); err != nil {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
return session
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// Set up some nodes in each DC that host the service.
|
|
|
|
{
|
|
|
|
for i := 0; i < 10; i++ {
|
2022-07-22 13:14:43 +00:00
|
|
|
for _, d := range []struct {
|
|
|
|
codec rpc.ClientCodec
|
|
|
|
dc string
|
|
|
|
}{
|
2023-03-27 22:40:49 +00:00
|
|
|
{es.server.codec, "dc1"},
|
|
|
|
{es.wanServer.codec, "dc2"},
|
|
|
|
{es.peeringServer.codec, "dc3"},
|
2022-07-22 13:14:43 +00:00
|
|
|
} {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.RegisterRequest{
|
2022-07-22 13:14:43 +00:00
|
|
|
Datacenter: d.dc,
|
2015-11-11 01:42:52 +00:00
|
|
|
Node: fmt.Sprintf("node%d", i+1),
|
|
|
|
Address: fmt.Sprintf("127.0.0.%d", i+1),
|
2017-01-18 23:40:19 +00:00
|
|
|
NodeMeta: map[string]string{
|
|
|
|
"group": fmt.Sprintf("%d", i/5),
|
2017-01-18 21:23:33 +00:00
|
|
|
"instance_type": "t2.micro",
|
|
|
|
},
|
2015-11-11 01:42:52 +00:00
|
|
|
Service: &structs.NodeService{
|
|
|
|
Service: "foo",
|
|
|
|
Port: 8000,
|
2022-07-22 13:14:43 +00:00
|
|
|
Tags: []string{d.dc, fmt.Sprintf("tag%d", i+1)},
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"svc-group": fmt.Sprintf("%d", i%2),
|
|
|
|
"foo": "true",
|
|
|
|
},
|
2015-11-11 01:42:52 +00:00
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
2017-01-18 21:23:33 +00:00
|
|
|
if i == 0 {
|
|
|
|
req.NodeMeta["unique"] = "true"
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
req.Service.Meta["unique"] = "true"
|
2017-01-18 21:23:33 +00:00
|
|
|
}
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
var reply struct{}
|
2022-07-22 13:14:43 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(d.codec, "Catalog.Register", &req, &reply); err != nil {
|
2015-11-11 01:42:52 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a service query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2016-12-10 19:19:08 +00:00
|
|
|
Name: "test",
|
2015-11-11 01:42:52 +00:00
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "foo",
|
|
|
|
},
|
|
|
|
DNS: structs.QueryDNSOptions{
|
|
|
|
TTL: "10s",
|
|
|
|
},
|
|
|
|
},
|
2016-02-24 09:26:16 +00:00
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
if err := msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID); err != nil {
|
2015-11-11 01:42:52 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run a query that doesn't exist.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("run query that doesn't exist", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: "nope",
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
err := msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply)
|
2020-07-01 00:49:13 +00:00
|
|
|
assert.EqualError(t, err, structs.ErrQueryNotFound.Error())
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 0)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes := func(t *testing.T, query *structs.PreparedQueryRequest, reply *structs.PreparedQueryExecuteResponse, n int) {
|
|
|
|
t.Helper()
|
|
|
|
assert.Len(t, reply.Nodes, n)
|
|
|
|
assert.Equal(t, "dc1", reply.Datacenter)
|
|
|
|
assert.Equal(t, 0, reply.Failovers)
|
|
|
|
assert.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
assert.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
assert.True(t, reply.QueryMeta.KnownLeader)
|
|
|
|
}
|
|
|
|
expectFailoverNodes := func(t *testing.T, query *structs.PreparedQueryRequest, reply *structs.PreparedQueryExecuteResponse, n int) {
|
|
|
|
t.Helper()
|
|
|
|
assert.Len(t, reply.Nodes, n)
|
|
|
|
assert.Equal(t, "dc2", reply.Datacenter)
|
|
|
|
assert.Equal(t, 1, reply.Failovers)
|
|
|
|
assert.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
assert.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
assert.True(t, reply.QueryMeta.KnownLeader)
|
|
|
|
}
|
|
|
|
|
2022-07-22 13:14:43 +00:00
|
|
|
expectFailoverPeerNodes := func(t *testing.T, query *structs.PreparedQueryRequest, reply *structs.PreparedQueryExecuteResponse, n int) {
|
|
|
|
t.Helper()
|
|
|
|
assert.Len(t, reply.Nodes, n)
|
|
|
|
assert.Equal(t, "", reply.Datacenter)
|
2023-03-27 22:40:49 +00:00
|
|
|
assert.Equal(t, es.peeringServer.acceptingPeerName, reply.PeerName)
|
2022-07-22 13:14:43 +00:00
|
|
|
assert.Equal(t, 2, reply.Failovers)
|
|
|
|
assert.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
assert.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
assert.True(t, reply.QueryMeta.KnownLeader)
|
|
|
|
}
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("run the registered query", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 10)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("try with a limit", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
Limit: 3,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 3)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2017-01-18 21:23:33 +00:00
|
|
|
// Run various service queries with node metadata filters.
|
2020-05-29 21:16:03 +00:00
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
filters map[string]string
|
|
|
|
numNodes int
|
|
|
|
}{
|
|
|
|
"no filter 10 nodes": {
|
|
|
|
filters: map[string]string{},
|
|
|
|
numNodes: 10,
|
|
|
|
},
|
|
|
|
"instance filter 10 nodes": {
|
|
|
|
filters: map[string]string{"instance_type": "t2.micro"},
|
|
|
|
numNodes: 10,
|
|
|
|
},
|
|
|
|
"group filter 5 nodes": {
|
|
|
|
filters: map[string]string{"group": "1"},
|
|
|
|
numNodes: 5,
|
|
|
|
},
|
|
|
|
"group filter unique 1 node": {
|
|
|
|
filters: map[string]string{"group": "0", "unique": "true"},
|
|
|
|
numNodes: 1,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
tc := tc
|
|
|
|
t.Run("node metadata - "+name, func(t *testing.T) {
|
|
|
|
session := newSessionDC1(t)
|
2017-01-18 21:23:33 +00:00
|
|
|
nodeMetaQuery := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2020-05-29 21:16:03 +00:00
|
|
|
Session: session,
|
2017-01-18 21:23:33 +00:00
|
|
|
Service: structs.ServiceQuery{
|
2017-01-18 23:40:19 +00:00
|
|
|
Service: "foo",
|
2017-01-18 21:23:33 +00:00
|
|
|
NodeMeta: tc.filters,
|
|
|
|
},
|
|
|
|
DNS: structs.QueryDNSOptions{
|
|
|
|
TTL: "10s",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &nodeMetaQuery, &nodeMetaQuery.Query.ID))
|
2017-01-18 21:23:33 +00:00
|
|
|
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: nodeMetaQuery.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2017-01-18 21:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, tc.numNodes)
|
2017-01-18 21:23:33 +00:00
|
|
|
|
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.True(t, structs.SatisfiesMetaFilters(node.Node.Meta, tc.filters), "meta: %v", node.Node.Meta)
|
2017-01-18 21:23:33 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2017-01-18 21:23:33 +00:00
|
|
|
}
|
|
|
|
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
// Run various service queries with service metadata filters
|
2020-05-29 21:16:03 +00:00
|
|
|
for name, tc := range map[string]struct {
|
|
|
|
filters map[string]string
|
|
|
|
numNodes int
|
|
|
|
}{
|
|
|
|
"no filter 10 nodes": {
|
|
|
|
filters: map[string]string{},
|
|
|
|
numNodes: 10,
|
|
|
|
},
|
|
|
|
"foo filter 10 nodes": {
|
|
|
|
filters: map[string]string{"foo": "true"},
|
|
|
|
numNodes: 10,
|
|
|
|
},
|
|
|
|
"group filter 0 - 5 nodes": {
|
|
|
|
filters: map[string]string{"svc-group": "0"},
|
|
|
|
numNodes: 5,
|
|
|
|
},
|
|
|
|
"group filter 1 - 5 nodes": {
|
|
|
|
filters: map[string]string{"svc-group": "1"},
|
|
|
|
numNodes: 5,
|
|
|
|
},
|
|
|
|
"group filter 0 - unique 1 node": {
|
|
|
|
filters: map[string]string{"svc-group": "0", "unique": "true"},
|
|
|
|
numNodes: 1,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
tc := tc
|
|
|
|
require.True(t, t.Run("service metadata - "+name, func(t *testing.T) {
|
|
|
|
session := newSessionDC1(t)
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
svcMetaQuery := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2020-05-29 21:16:03 +00:00
|
|
|
Session: session,
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "foo",
|
|
|
|
ServiceMeta: tc.filters,
|
|
|
|
},
|
|
|
|
DNS: structs.QueryDNSOptions{
|
|
|
|
TTL: "10s",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
|
|
|
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &svcMetaQuery, &svcMetaQuery.Query.ID))
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: svcMetaQuery.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, tc.numNodes)
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.True(t, structs.SatisfiesMetaFilters(node.Service.Meta, tc.filters), "meta: %v", node.Service.Meta)
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
}))
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
}
|
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// Push a coordinate for one of the nodes so we can try an RTT sort. We
|
|
|
|
// have to sleep a little while for the coordinate batch to get flushed.
|
|
|
|
{
|
|
|
|
req := structs.CoordinateUpdateRequest{
|
2020-05-29 21:16:03 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
|
|
|
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
var out struct{}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "Coordinate.Update", &req, &out))
|
|
|
|
time.Sleep(3 * es.server.server.config.CoordinateUpdatePeriod)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Try an RTT sort. We don't have any other coordinates in there but
|
|
|
|
// showing that the node with a coordinate is always first proves we
|
|
|
|
// call the RTT sorting function, which is tested elsewhere.
|
|
|
|
for i := 0; i < 100; i++ {
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run(fmt.Sprintf("rtt sort iter %d", i), func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
Source: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
|
|
|
},
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2020-05-29 21:16:03 +00:00
|
|
|
}
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 10)
|
|
|
|
assert.Equal(t, "node3", reply.Nodes[0].Node.Node)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the shuffle looks like it's working.
|
|
|
|
uniques := make(map[string]struct{})
|
|
|
|
for i := 0; i < 100; i++ {
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run(fmt.Sprintf("shuffle iter %d", i), func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2020-05-29 21:16:03 +00:00
|
|
|
}
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 10)
|
|
|
|
|
|
|
|
var names []string
|
|
|
|
for _, node := range reply.Nodes {
|
|
|
|
names = append(names, node.Node.Node)
|
|
|
|
}
|
|
|
|
key := strings.Join(names, "|")
|
|
|
|
uniques[key] = struct{}{}
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We have to allow for the fact that there won't always be a unique
|
|
|
|
// shuffle each pass, so we just look for smell here without the test
|
|
|
|
// being flaky.
|
|
|
|
if len(uniques) < 50 {
|
|
|
|
t.Fatalf("unique shuffle ratio too low: %d/100", len(uniques))
|
|
|
|
}
|
|
|
|
|
2016-06-30 19:11:20 +00:00
|
|
|
// Set the query to return results nearest to node3. This is the only
|
|
|
|
// node with coordinates, and it carries the service we are asking for,
|
|
|
|
// so node3 should always show up first.
|
2016-06-20 21:53:13 +00:00
|
|
|
query.Op = structs.PreparedQueryUpdate
|
2016-06-30 19:11:20 +00:00
|
|
|
query.Query.Service.Near = "node3"
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2016-06-20 21:53:13 +00:00
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Now run the query and make sure the sort looks right.
|
2020-05-29 21:16:03 +00:00
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
t.Run(fmt.Sprintf("run nearest query iter %d", i), func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Agent: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
|
|
|
},
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2020-05-29 21:16:03 +00:00
|
|
|
}
|
2016-06-20 21:53:13 +00:00
|
|
|
|
2017-01-18 23:40:19 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
|
|
|
assert.Equal(t, "node3", reply.Nodes[0].Node.Node)
|
|
|
|
})
|
2016-06-20 21:53:13 +00:00
|
|
|
}
|
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Query again, but this time set a client-supplied query source. This
|
|
|
|
// proves that we allow overriding the baked-in value with ?near.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("nearest fallback to shuffle", func(t *testing.T) {
|
2016-06-30 19:11:20 +00:00
|
|
|
// Set up the query with a non-existent node. This will cause the
|
|
|
|
// nodes to be shuffled if the passed node is respected, proving
|
|
|
|
// that we allow the override to happen.
|
2016-06-21 19:39:40 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
2016-06-30 19:11:20 +00:00
|
|
|
Source: structs.QuerySource{
|
2016-06-30 23:51:18 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "foo",
|
|
|
|
},
|
|
|
|
Agent: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
2016-06-21 22:34:26 +00:00
|
|
|
},
|
2016-06-21 19:39:40 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-06-21 19:39:40 +00:00
|
|
|
}
|
|
|
|
|
2016-06-30 19:11:20 +00:00
|
|
|
shuffled := false
|
|
|
|
for i := 0; i < 10; i++ {
|
2017-01-24 02:11:13 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
|
|
|
|
2016-06-30 19:11:20 +00:00
|
|
|
if node := reply.Nodes[0].Node.Node; node != "node3" {
|
|
|
|
shuffled = true
|
|
|
|
break
|
|
|
|
}
|
2016-06-21 19:39:40 +00:00
|
|
|
}
|
2016-06-30 19:11:20 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
require.True(t, shuffled, "expect nodes to be shuffled")
|
|
|
|
})
|
2016-06-21 19:39:40 +00:00
|
|
|
|
2016-07-01 21:28:58 +00:00
|
|
|
// If the exact node we are sorting near appears in the list, make sure it
|
|
|
|
// gets popped to the front of the result.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("nearest bypasses shuffle", func(t *testing.T) {
|
2016-07-01 21:28:58 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Source: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node1",
|
|
|
|
},
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-07-01 21:28:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < 10; i++ {
|
2017-01-18 23:40:19 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
|
|
|
assert.Equal(t, "node1", reply.Nodes[0].Node.Node)
|
2016-07-01 21:28:58 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2016-07-01 21:28:58 +00:00
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Bake the magic "_agent" flag into the query.
|
|
|
|
query.Query.Service.Near = "_agent"
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2016-06-30 23:51:18 +00:00
|
|
|
|
|
|
|
// Check that we sort the local agent first when the magic flag is set.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("local agent is first using _agent on node3", func(t *testing.T) {
|
2016-06-21 22:34:26 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
2016-06-30 23:51:18 +00:00
|
|
|
Agent: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
2016-06-21 22:34:26 +00:00
|
|
|
},
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-06-21 22:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < 10; i++ {
|
2017-01-18 23:40:19 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
|
|
|
assert.Equal(t, "node3", reply.Nodes[0].Node.Node)
|
2016-06-21 22:34:26 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2016-06-21 19:54:18 +00:00
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Check that the query isn't just sorting "node3" first because we
|
|
|
|
// provided it in the Agent query source. Proves that we use the
|
|
|
|
// Agent source when the magic "_agent" flag is passed.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("local agent is first using _agent on foo", func(t *testing.T) {
|
2016-06-21 19:54:18 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
2016-06-30 23:51:18 +00:00
|
|
|
Agent: structs.QuerySource{
|
2016-06-21 22:34:26 +00:00
|
|
|
Datacenter: "dc1",
|
2016-06-30 23:51:18 +00:00
|
|
|
Node: "foo",
|
2016-06-21 22:34:26 +00:00
|
|
|
},
|
2016-06-21 19:54:18 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-06-21 19:54:18 +00:00
|
|
|
}
|
|
|
|
|
2016-06-30 23:51:18 +00:00
|
|
|
// Expect the set to be shuffled since we have no coordinates
|
|
|
|
// on the "foo" node.
|
|
|
|
shuffled := false
|
2016-06-21 19:54:18 +00:00
|
|
|
for i := 0; i < 10; i++ {
|
2017-01-18 23:40:19 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
2016-06-21 19:54:18 +00:00
|
|
|
if node := reply.Nodes[0].Node.Node; node != "node3" {
|
2016-06-30 23:51:18 +00:00
|
|
|
shuffled = true
|
|
|
|
break
|
2016-06-30 19:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-30 23:51:18 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
require.True(t, shuffled, "expect nodes to be shuffled")
|
|
|
|
})
|
2016-06-30 19:11:20 +00:00
|
|
|
|
|
|
|
// Shuffles if the response comes from a non-local DC. Proves that the
|
2016-06-30 23:51:18 +00:00
|
|
|
// agent query source does not interfere with the order.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("shuffles if coming from non-local dc", func(t *testing.T) {
|
2016-06-30 19:11:20 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Source: structs.QuerySource{
|
|
|
|
Datacenter: "dc2",
|
|
|
|
Node: "node3",
|
|
|
|
},
|
2016-06-30 23:51:18 +00:00
|
|
|
Agent: structs.QuerySource{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node3",
|
|
|
|
},
|
2016-06-30 19:11:20 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-06-30 19:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
shuffled := false
|
|
|
|
for i := 0; i < 10; i++ {
|
2017-01-18 23:40:19 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.Len(t, reply.Nodes, 10)
|
2016-06-30 19:11:20 +00:00
|
|
|
if reply.Nodes[0].Node.Node != "node3" {
|
|
|
|
shuffled = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
require.True(t, shuffled, "expect node shuffle for remote results")
|
|
|
|
})
|
2016-06-21 19:54:18 +00:00
|
|
|
|
2016-06-30 19:11:20 +00:00
|
|
|
// Un-bake the near parameter.
|
2016-06-21 19:39:40 +00:00
|
|
|
query.Query.Service.Near = ""
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2016-06-20 21:53:13 +00:00
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// Update the health of a node to mark it critical.
|
2023-03-22 13:24:13 +00:00
|
|
|
setHealth := func(t *testing.T, codec rpc.ClientCodec, dc string, i int, health string) {
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Helper()
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.RegisterRequest{
|
2022-07-22 13:14:43 +00:00
|
|
|
Datacenter: dc,
|
2023-03-22 13:24:13 +00:00
|
|
|
Node: fmt.Sprintf("node%d", i),
|
2015-11-11 01:42:52 +00:00
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
Service: "foo",
|
|
|
|
Port: 8000,
|
2023-03-22 13:24:13 +00:00
|
|
|
Tags: []string{dc, fmt.Sprintf("tag%d", i)},
|
2015-11-11 01:42:52 +00:00
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
Name: "failing",
|
|
|
|
Status: health,
|
|
|
|
ServiceID: "foo",
|
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
|
|
|
var reply struct{}
|
2022-07-22 13:14:43 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
setHealth(t, es.server.codec, "dc1", 1, api.HealthCritical)
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// The failing node should be filtered.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("failing node filtered", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 9)
|
2015-11-11 01:42:52 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node1", node.Node.Node)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Upgrade it to a warning and re-query, should be 10 nodes again.
|
2023-03-27 22:40:49 +00:00
|
|
|
setHealth(t, es.server.codec, "dc1", 1, api.HealthWarning)
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("warning nodes are included", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 10)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Make the query more picky so it excludes warning nodes.
|
|
|
|
query.Query.Service.OnlyPassing = true
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// The node in the warning state should be filtered.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("warning nodes are omitted with onlypassing=true", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 9)
|
2015-11-11 01:42:52 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node1", node.Node.Node)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2018-04-10 12:28:27 +00:00
|
|
|
// Make the query ignore all our health checks (which have "failing" ID
|
|
|
|
// implicitly from their name).
|
|
|
|
query.Query.Service.IgnoreCheckIDs = []types.CheckID{"failing"}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2018-04-10 12:28:27 +00:00
|
|
|
|
|
|
|
// We should end up with 10 nodes again
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("all nodes including when ignoring failing checks", func(t *testing.T) {
|
2018-04-10 12:28:27 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2018-04-10 12:28:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2018-04-10 12:28:27 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 10)
|
|
|
|
})
|
2018-04-10 12:28:27 +00:00
|
|
|
|
|
|
|
// Undo that so all the following tests aren't broken!
|
|
|
|
query.Query.Service.IgnoreCheckIDs = nil
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2018-04-10 12:28:27 +00:00
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// Make the query more picky by adding a tag filter. This just proves we
|
|
|
|
// call into the tag filter, it is tested more thoroughly in a separate
|
|
|
|
// test.
|
|
|
|
query.Query.Service.Tags = []string{"!tag3"}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// The node in the warning state should be filtered as well as the node
|
|
|
|
// with the filtered tag.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("filter node in warning state and filtered node", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 8)
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node1", node.Node.Node)
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
// Make sure the query gets denied with this token.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("query denied with deny token", func(t *testing.T) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.denyToken},
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 0)
|
|
|
|
})
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
// Bake the exec token into the query.
|
2023-03-27 22:40:49 +00:00
|
|
|
query.Query.Token = es.execToken
|
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
// Now even querying with the deny token should work.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("query with deny token still works", func(t *testing.T) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.denyToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 8)
|
2015-11-11 01:42:52 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node1", node.Node.Node)
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Un-bake the token.
|
|
|
|
query.Query.Token = ""
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
// Make sure the query gets denied again with the deny token.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("denied with deny token when no query token", func(t *testing.T) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.denyToken},
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 0)
|
|
|
|
})
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("filter nodes with exec token without node privileges", func(t *testing.T) {
|
2016-12-13 01:27:22 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execNoNodesToken},
|
2016-12-13 01:27:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2016-12-13 01:27:22 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 0)
|
2021-12-03 23:04:09 +00:00
|
|
|
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2016-12-13 01:27:22 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("normal operation again with exec token", func(t *testing.T) {
|
2016-12-13 01:27:22 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2016-12-13 01:27:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2016-12-13 01:27:22 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 8)
|
2016-12-13 01:27:22 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node1", node.Node.Node)
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
2016-12-13 01:27:22 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2016-12-13 01:27:22 +00:00
|
|
|
|
2015-11-11 01:42:52 +00:00
|
|
|
// Now fail everything in dc1 and we should get an empty list back.
|
|
|
|
for i := 0; i < 10; i++ {
|
2023-03-27 22:40:49 +00:00
|
|
|
setHealth(t, es.server.codec, "dc1", i+1, api.HealthCritical)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("everything is failing so should get empty list", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectNodes(t, &query, &reply, 0)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Modify the query to have it fail over to a bogus DC and then dc2.
|
|
|
|
query.Query.Service.Failover.Datacenters = []string{"bogus", "dc2"}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Now we should see 9 nodes from dc2 (we have the tag filter still).
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("see 9 nodes from dc2 using tag filter", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectFailoverNodes(t, &query, &reply, 9)
|
2015-11-11 01:42:52 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Make sure the limit and query options are forwarded.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("forward limit and query options", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
Limit: 3,
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
QueryOptions: structs.QueryOptions{
|
2023-03-27 22:40:49 +00:00
|
|
|
Token: es.execToken,
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
RequireConsistent: true,
|
|
|
|
},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectFailoverNodes(t, &query, &reply, 3)
|
2015-11-11 01:42:52 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
|
|
|
// Make sure the remote shuffle looks like it's working.
|
|
|
|
uniques = make(map[string]struct{})
|
|
|
|
for i := 0; i < 100; i++ {
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run(fmt.Sprintf("remote shuffle iter %d", i), func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2020-05-29 21:16:03 +00:00
|
|
|
}
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectFailoverNodes(t, &query, &reply, 9)
|
|
|
|
var names []string
|
|
|
|
for _, node := range reply.Nodes {
|
|
|
|
names = append(names, node.Node.Node)
|
|
|
|
}
|
|
|
|
key := strings.Join(names, "|")
|
|
|
|
uniques[key] = struct{}{}
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We have to allow for the fact that there won't always be a unique
|
|
|
|
// shuffle each pass, so we just look for smell here without the test
|
|
|
|
// being flaky.
|
|
|
|
if len(uniques) < 50 {
|
|
|
|
t.Fatalf("unique shuffle ratio too low: %d/100", len(uniques))
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Make sure the query response from dc2 gets denied with the deny token.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("query from dc2 denied with deny token", func(t *testing.T) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.denyToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectFailoverNodes(t, &query, &reply, 0)
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2021-12-03 23:04:09 +00:00
|
|
|
t.Run("nodes in response from dc2 are filtered by ACL token", func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execNoNodesToken},
|
2021-12-03 23:04:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2021-12-03 23:04:09 +00:00
|
|
|
|
|
|
|
expectFailoverNodes(t, &query, &reply, 0)
|
|
|
|
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
|
|
|
})
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// Bake the exec token into the query.
|
2023-03-27 22:40:49 +00:00
|
|
|
query.Query.Token = es.execToken
|
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
|
|
|
|
// Now even querying with the deny token should work.
|
2020-05-29 21:16:03 +00:00
|
|
|
t.Run("query from dc2 with exec token using deny token works", func(t *testing.T) {
|
2015-11-11 01:42:52 +00:00
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.denyToken},
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2015-11-11 01:42:52 +00:00
|
|
|
|
2020-05-29 21:16:03 +00:00
|
|
|
expectFailoverNodes(t, &query, &reply, 9)
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
for _, node := range reply.Nodes {
|
2020-05-29 21:16:03 +00:00
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
2020-05-29 21:16:03 +00:00
|
|
|
})
|
2022-07-22 13:14:43 +00:00
|
|
|
|
|
|
|
// Modify the query to have it fail over to a bogus DC and then dc2.
|
|
|
|
query.Query.Service.Failover = structs.QueryFailoverOptions{
|
|
|
|
Targets: []structs.QueryFailoverTarget{
|
|
|
|
{Datacenter: "dc2"},
|
2023-03-27 22:40:49 +00:00
|
|
|
{Peer: es.peeringServer.acceptingPeerName},
|
2022-07-22 13:14:43 +00:00
|
|
|
},
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
2022-07-22 13:14:43 +00:00
|
|
|
|
|
|
|
// Ensure the foo service has fully replicated.
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
2023-03-27 22:40:49 +00:00
|
|
|
_, nodes, err := es.server.server.fsm.State().CheckServiceNodes(nil, "foo", nil, es.peeringServer.acceptingPeerName)
|
2022-07-22 13:14:43 +00:00
|
|
|
require.NoError(r, err)
|
|
|
|
require.Len(r, nodes, 10)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Now we should see 9 nodes from dc2
|
|
|
|
t.Run("failing over to cluster peers", func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2022-07-22 13:14:43 +00:00
|
|
|
|
|
|
|
for _, node := range reply.Nodes {
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
|
|
|
}
|
|
|
|
expectFailoverNodes(t, &query, &reply, 9)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Set all checks in dc2 as critical
|
|
|
|
for i := 0; i < 10; i++ {
|
2023-03-27 22:40:49 +00:00
|
|
|
setHealth(t, es.wanServer.codec, "dc2", i+1, api.HealthCritical)
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now we should see 9 nodes from dc3 (we have the tag filter still)
|
|
|
|
t.Run("failing over to cluster peers", func(t *testing.T) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2022-07-22 13:14:43 +00:00
|
|
|
|
|
|
|
for _, node := range reply.Nodes {
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
|
|
|
}
|
|
|
|
expectFailoverPeerNodes(t, &query, &reply, 9)
|
|
|
|
})
|
2023-03-22 13:24:13 +00:00
|
|
|
|
|
|
|
// Set all checks in dc1 as passing
|
|
|
|
for i := 0; i < 10; i++ {
|
2023-03-27 22:40:49 +00:00
|
|
|
setHealth(t, es.server.codec, "dc1", i+1, api.HealthPassing)
|
2023-03-22 13:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing is healthy so nothing is returned
|
|
|
|
t.Run("un-failing over", func(t *testing.T) {
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
2023-03-27 22:40:49 +00:00
|
|
|
QueryOptions: structs.QueryOptions{Token: es.execToken},
|
2023-03-22 13:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2023-03-27 22:40:49 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.server.codec, "PreparedQuery.Execute", &req, &reply))
|
2023-03-22 13:24:13 +00:00
|
|
|
|
|
|
|
for _, node := range reply.Nodes {
|
|
|
|
assert.NotEqual(t, "node3", node.Node.Node)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectNodes(t, &query, &reply, 9)
|
|
|
|
})
|
|
|
|
})
|
2015-11-11 01:42:52 +00:00
|
|
|
}
|
|
|
|
|
2015-11-10 23:16:41 +00:00
|
|
|
func TestPreparedQuery_Execute_ForwardLeader(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-10 23:16:41 +00:00
|
|
|
dir1, s1 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec1 := rpcClient(t, s1)
|
|
|
|
defer codec1.Close()
|
|
|
|
|
|
|
|
dir2, s2 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir2)
|
|
|
|
defer s2.Shutdown()
|
|
|
|
codec2 := rpcClient(t, s2)
|
|
|
|
defer codec2.Close()
|
|
|
|
|
|
|
|
// Try to join.
|
2017-05-05 10:29:49 +00:00
|
|
|
joinLAN(t, s2, s1)
|
2015-11-10 23:16:41 +00:00
|
|
|
|
2017-04-19 23:00:11 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc1")
|
2015-11-10 23:16:41 +00:00
|
|
|
|
|
|
|
// Use the follower as the client.
|
|
|
|
var codec rpc.ClientCodec
|
|
|
|
if !s1.IsLeader() {
|
|
|
|
codec = codec1
|
|
|
|
} else {
|
|
|
|
codec = codec2
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a node and service in the catalog.
|
|
|
|
{
|
|
|
|
req := structs.RegisterRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "foo",
|
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
Service: "redis",
|
2022-01-20 12:47:50 +00:00
|
|
|
Tags: []string{"primary"},
|
2015-11-10 23:16:41 +00:00
|
|
|
Port: 8000,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply struct{}
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a bare bones query.
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
2016-12-10 19:19:08 +00:00
|
|
|
Name: "test",
|
2015-11-10 23:16:41 +00:00
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "redis",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var reply string
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute it through the follower.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: reply,
|
|
|
|
}
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Execute", &req, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(reply.Nodes) != 1 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute it through the follower with consistency turned on.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: reply,
|
|
|
|
QueryOptions: structs.QueryOptions{RequireConsistent: true},
|
|
|
|
}
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Execute", &req, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(reply.Nodes) != 1 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remote execute it through the follower.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRemoteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Query: *query.Query,
|
|
|
|
}
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.ExecuteRemote", &req, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(reply.Nodes) != 1 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remote execute it through the follower with consistency turned on.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRemoteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Query: *query.Query,
|
|
|
|
QueryOptions: structs.QueryOptions{RequireConsistent: true},
|
|
|
|
}
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.ExecuteRemote", &req, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(reply.Nodes) != 1 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-11 02:23:37 +00:00
|
|
|
|
2018-06-05 21:17:19 +00:00
|
|
|
func TestPreparedQuery_Execute_ConnectExact(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2018-06-05 21:17:19 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
dir1, s1 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
codec := rpcClient(t, s1)
|
|
|
|
defer codec.Close()
|
|
|
|
|
|
|
|
// Setup 3 services on 3 nodes: one is non-Connect, one is Connect native,
|
|
|
|
// and one is a proxy to the non-Connect one.
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
req := structs.RegisterRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: fmt.Sprintf("node%d", i+1),
|
|
|
|
Address: fmt.Sprintf("127.0.0.%d", i+1),
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
Service: "foo",
|
|
|
|
Port: 8000,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i {
|
|
|
|
case 0:
|
|
|
|
// Default do nothing
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
// Connect native
|
|
|
|
req.Service.Connect.Native = true
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
// Connect proxy
|
|
|
|
req.Service.Kind = structs.ServiceKindConnectProxy
|
2018-09-12 16:07:47 +00:00
|
|
|
req.Service.Proxy.DestinationServiceName = req.Service.Service
|
2018-06-05 21:17:19 +00:00
|
|
|
req.Service.Service = "proxy"
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply struct{}
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply))
|
2018-06-05 21:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The query, start with connect disabled
|
|
|
|
query := structs.PreparedQueryRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Op: structs.PreparedQueryCreate,
|
|
|
|
Query: &structs.PreparedQuery{
|
|
|
|
Name: "test",
|
|
|
|
Service: structs.ServiceQuery{
|
|
|
|
Service: "foo",
|
|
|
|
},
|
|
|
|
DNS: structs.QueryDNSOptions{
|
|
|
|
TTL: "10s",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:17:19 +00:00
|
|
|
codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
|
|
|
|
|
|
|
// In the future we'll run updates
|
|
|
|
query.Op = structs.PreparedQueryUpdate
|
|
|
|
|
|
|
|
// Run the registered query.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:17:19 +00:00
|
|
|
codec, "PreparedQuery.Execute", &req, &reply))
|
|
|
|
|
|
|
|
// Result should have two because it omits the proxy whose name
|
|
|
|
// doesn't match the query.
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.Len(t, reply.Nodes, 2)
|
|
|
|
require.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
require.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
require.True(t, reply.QueryMeta.KnownLeader, "queried leader")
|
2018-06-05 21:17:19 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 21:42:01 +00:00
|
|
|
// Run with the Connect setting specified on the request
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
Connect: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:42:01 +00:00
|
|
|
codec, "PreparedQuery.Execute", &req, &reply))
|
|
|
|
|
|
|
|
// Result should have two because we should get the native AND
|
|
|
|
// the proxy (since the destination matches our service name).
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.Len(t, reply.Nodes, 2)
|
|
|
|
require.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
require.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
require.True(t, reply.QueryMeta.KnownLeader, "queried leader")
|
2018-06-05 21:42:01 +00:00
|
|
|
|
|
|
|
// Make sure the native is the first one
|
|
|
|
if !reply.Nodes[0].Service.Connect.Native {
|
|
|
|
reply.Nodes[0], reply.Nodes[1] = reply.Nodes[1], reply.Nodes[0]
|
|
|
|
}
|
|
|
|
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.True(t, reply.Nodes[0].Service.Connect.Native, "native")
|
|
|
|
require.Equal(t, reply.Service, reply.Nodes[0].Service.Service)
|
2018-06-05 21:42:01 +00:00
|
|
|
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.Equal(t, structs.ServiceKindConnectProxy, reply.Nodes[1].Service.Kind)
|
|
|
|
require.Equal(t, reply.Service, reply.Nodes[1].Service.Proxy.DestinationServiceName)
|
2018-06-05 21:42:01 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 21:17:19 +00:00
|
|
|
// Update the query
|
|
|
|
query.Query.Service.Connect = true
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:17:19 +00:00
|
|
|
codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
|
|
|
|
|
|
|
// Run the registered query.
|
|
|
|
{
|
|
|
|
req := structs.PreparedQueryExecuteRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
QueryIDOrName: query.Query.ID,
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:17:19 +00:00
|
|
|
codec, "PreparedQuery.Execute", &req, &reply))
|
|
|
|
|
|
|
|
// Result should have two because we should get the native AND
|
|
|
|
// the proxy (since the destination matches our service name).
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.Len(t, reply.Nodes, 2)
|
|
|
|
require.Equal(t, query.Query.Service.Service, reply.Service)
|
|
|
|
require.Equal(t, query.Query.DNS, reply.DNS)
|
|
|
|
require.True(t, reply.QueryMeta.KnownLeader, "queried leader")
|
2018-06-05 21:17:19 +00:00
|
|
|
|
|
|
|
// Make sure the native is the first one
|
|
|
|
if !reply.Nodes[0].Service.Connect.Native {
|
|
|
|
reply.Nodes[0], reply.Nodes[1] = reply.Nodes[1], reply.Nodes[0]
|
|
|
|
}
|
|
|
|
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.True(t, reply.Nodes[0].Service.Connect.Native, "native")
|
|
|
|
require.Equal(t, reply.Service, reply.Nodes[0].Service.Service)
|
2018-06-05 21:17:19 +00:00
|
|
|
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.Equal(t, structs.ServiceKindConnectProxy, reply.Nodes[1].Service.Kind)
|
|
|
|
require.Equal(t, reply.Service, reply.Nodes[1].Service.Proxy.DestinationServiceName)
|
2018-06-05 21:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unset the query
|
|
|
|
query.Query.Service.Connect = false
|
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
2022-01-20 16:46:23 +00:00
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(
|
2018-06-05 21:17:19 +00:00
|
|
|
codec, "PreparedQuery.Apply", &query, &query.Query.ID))
|
|
|
|
}
|
|
|
|
|
2015-11-11 02:23:37 +00:00
|
|
|
func TestPreparedQuery_tagFilter(t *testing.T) {
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-11 02:23:37 +00:00
|
|
|
testNodes := func() structs.CheckServiceNodes {
|
|
|
|
return structs.CheckServiceNodes{
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node1"},
|
|
|
|
Service: &structs.NodeService{Tags: []string{"foo"}},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node2"},
|
|
|
|
Service: &structs.NodeService{Tags: []string{"foo", "BAR"}},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node3"},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node4"},
|
|
|
|
Service: &structs.NodeService{Tags: []string{"foo", "baz"}},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node5"},
|
|
|
|
Service: &structs.NodeService{Tags: []string{"foo", "zoo"}},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node6"},
|
|
|
|
Service: &structs.NodeService{Tags: []string{"bar"}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This always sorts so that it's not annoying to compare after the swap
|
|
|
|
// operations that the algorithm performs.
|
|
|
|
stringify := func(nodes structs.CheckServiceNodes) string {
|
|
|
|
var names []string
|
|
|
|
for _, node := range nodes {
|
|
|
|
names = append(names, node.Node.Node)
|
|
|
|
}
|
|
|
|
sort.Strings(names)
|
|
|
|
return strings.Join(names, "|")
|
|
|
|
}
|
|
|
|
|
|
|
|
ret := stringify(tagFilter([]string{}, testNodes()))
|
|
|
|
if ret != "node1|node2|node3|node4|node5|node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"foo"}, testNodes()))
|
|
|
|
if ret != "node1|node2|node4|node5" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"!foo"}, testNodes()))
|
|
|
|
if ret != "node3|node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"!foo", "bar"}, testNodes()))
|
|
|
|
if ret != "node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"!foo", "!bar"}, testNodes()))
|
|
|
|
if ret != "node3" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"nope"}, testNodes()))
|
|
|
|
if ret != "" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"bar"}, testNodes()))
|
|
|
|
if ret != "node2|node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"BAR"}, testNodes()))
|
|
|
|
if ret != "node2|node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{"bAr"}, testNodes()))
|
|
|
|
if ret != "node2|node6" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
2016-02-26 08:25:44 +00:00
|
|
|
|
|
|
|
ret = stringify(tagFilter([]string{""}, testNodes()))
|
|
|
|
if ret != "" {
|
|
|
|
t.Fatalf("bad: %s", ret)
|
|
|
|
}
|
2015-11-11 02:23:37 +00:00
|
|
|
}
|
2015-11-11 02:30:12 +00:00
|
|
|
|
|
|
|
func TestPreparedQuery_Wrapper(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2015-11-11 02:30:12 +00:00
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2015-11-11 02:30:12 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
|
|
|
|
|
|
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
|
|
c.Datacenter = "dc2"
|
2021-08-06 22:00:58 +00:00
|
|
|
c.PrimaryDatacenter = "dc1"
|
2018-10-19 16:04:07 +00:00
|
|
|
c.ACLsEnabled = true
|
2021-12-07 12:39:28 +00:00
|
|
|
c.ACLInitialManagementToken = "root"
|
2021-08-06 22:39:39 +00:00
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
2015-11-11 02:30:12 +00:00
|
|
|
})
|
|
|
|
defer os.RemoveAll(dir2)
|
|
|
|
defer s2.Shutdown()
|
|
|
|
|
2019-11-25 17:07:04 +00:00
|
|
|
s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig)
|
2020-05-29 21:16:03 +00:00
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
|
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc2", testrpc.WithToken("root"))
|
2020-01-28 23:50:41 +00:00
|
|
|
|
2015-11-11 02:30:12 +00:00
|
|
|
// Try to WAN join.
|
2017-05-05 10:29:49 +00:00
|
|
|
joinWAN(t, s2, s1)
|
2015-11-11 02:30:12 +00:00
|
|
|
|
|
|
|
// Try all the operations on a real server via the wrapper.
|
2022-07-22 13:14:43 +00:00
|
|
|
wrapper := &queryServerWrapper{srv: s1, executeRemote: func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
return nil
|
|
|
|
}}
|
2020-01-28 23:50:41 +00:00
|
|
|
wrapper.GetLogger().Debug("Test")
|
2015-11-11 02:30:12 +00:00
|
|
|
|
|
|
|
ret, err := wrapper.GetOtherDatacentersByDistance()
|
2020-01-28 23:50:41 +00:00
|
|
|
wrapper.GetLogger().Info("Returned value", "value", ret)
|
2015-11-11 02:30:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(ret) != 1 || ret[0] != "dc2" {
|
|
|
|
t.Fatalf("bad: %v", ret)
|
|
|
|
}
|
2018-08-09 16:40:07 +00:00
|
|
|
// Since we have no idea when the joinWAN operation completes
|
|
|
|
// we keep on querying until the the join operation completes.
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
r.Check(s1.forwardDC("Status.Ping", "dc2", &struct{}{}, &struct{}{}))
|
|
|
|
})
|
2015-11-11 02:30:12 +00:00
|
|
|
}
|
2015-11-11 05:16:04 +00:00
|
|
|
|
2023-04-24 20:21:28 +00:00
|
|
|
var _ queryServer = (*mockQueryServer)(nil)
|
|
|
|
|
2015-11-11 05:16:04 +00:00
|
|
|
type mockQueryServer struct {
|
2023-04-24 20:21:28 +00:00
|
|
|
queryServerWrapper
|
2015-11-11 05:16:04 +00:00
|
|
|
Datacenters []string
|
|
|
|
DatacentersError error
|
|
|
|
QueryLog []string
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error
|
2020-01-28 23:50:41 +00:00
|
|
|
Logger hclog.Logger
|
2015-11-11 05:16:04 +00:00
|
|
|
LogBuffer *bytes.Buffer
|
2023-04-24 20:21:28 +00:00
|
|
|
SamenessGroup map[string]*structs.SamenessGroupConfigEntry
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockQueryServer) JoinQueryLog() string {
|
|
|
|
return strings.Join(m.QueryLog, "|")
|
|
|
|
}
|
|
|
|
|
2020-01-28 23:50:41 +00:00
|
|
|
func (m *mockQueryServer) GetLogger() hclog.Logger {
|
2015-11-11 05:16:04 +00:00
|
|
|
if m.Logger == nil {
|
|
|
|
m.LogBuffer = new(bytes.Buffer)
|
2020-01-28 23:50:41 +00:00
|
|
|
|
|
|
|
m.Logger = hclog.New(&hclog.LoggerOptions{
|
|
|
|
Name: "mock_query",
|
|
|
|
Output: m.LogBuffer,
|
2022-02-03 22:07:39 +00:00
|
|
|
Level: hclog.Debug,
|
2020-01-28 23:50:41 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return m.Logger
|
|
|
|
}
|
|
|
|
|
2022-07-22 13:14:43 +00:00
|
|
|
func (m *mockQueryServer) GetLocalDC() string {
|
2023-04-24 20:21:28 +00:00
|
|
|
return localTestDC
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
|
|
|
|
2015-11-11 05:16:04 +00:00
|
|
|
func (m *mockQueryServer) GetOtherDatacentersByDistance() ([]string, error) {
|
|
|
|
return m.Datacenters, m.DatacentersError
|
|
|
|
}
|
|
|
|
|
2022-07-22 13:14:43 +00:00
|
|
|
func (m *mockQueryServer) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
2022-10-04 18:46:15 +00:00
|
|
|
peerName := args.Query.Service.Peer
|
2023-04-24 20:21:28 +00:00
|
|
|
partitionName := args.Query.Service.PartitionOrEmpty()
|
|
|
|
namespaceName := args.Query.Service.NamespaceOrEmpty()
|
2022-07-22 13:14:43 +00:00
|
|
|
dc := args.Datacenter
|
|
|
|
if peerName != "" {
|
|
|
|
m.QueryLog = append(m.QueryLog, fmt.Sprintf("peer:%s", peerName))
|
2023-04-24 20:21:28 +00:00
|
|
|
} else if partitionName != "" {
|
|
|
|
m.QueryLog = append(m.QueryLog, fmt.Sprintf("partition:%s", partitionName))
|
|
|
|
} else if namespaceName != "" {
|
|
|
|
m.QueryLog = append(m.QueryLog, fmt.Sprintf("namespace:%s", namespaceName))
|
2022-07-22 13:14:43 +00:00
|
|
|
} else {
|
|
|
|
m.QueryLog = append(m.QueryLog, fmt.Sprintf("%s:%s", dc, "PreparedQuery.ExecuteRemote"))
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
2022-07-22 13:14:43 +00:00
|
|
|
reply.PeerName = peerName
|
|
|
|
reply.Datacenter = dc
|
2023-04-24 20:21:28 +00:00
|
|
|
reply.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(partitionName, namespaceName)
|
2022-07-22 13:14:43 +00:00
|
|
|
|
2015-11-11 05:16:04 +00:00
|
|
|
if m.QueryFn != nil {
|
2022-07-22 13:14:43 +00:00
|
|
|
return m.QueryFn(args, reply)
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
2017-04-21 01:59:42 +00:00
|
|
|
return nil
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
|
2023-04-24 20:21:28 +00:00
|
|
|
type mockStateLookup struct {
|
|
|
|
SamenessGroup map[string]*structs.SamenessGroupConfigEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sl mockStateLookup) samenessGroupLookup(name string, entMeta acl.EnterpriseMeta) (uint64, *structs.SamenessGroupConfigEntry, error) {
|
|
|
|
lookup := name
|
|
|
|
if ap := entMeta.PartitionOrEmpty(); ap != "" {
|
|
|
|
lookup = fmt.Sprintf("%s-%s", lookup, ap)
|
|
|
|
} else if ns := entMeta.NamespaceOrEmpty(); ns != "" {
|
|
|
|
lookup = fmt.Sprintf("%s-%s", lookup, ns)
|
|
|
|
}
|
|
|
|
|
|
|
|
sg, ok := sl.SamenessGroup[lookup]
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, errors.New("unable to find sameness group")
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, sg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockQueryServer) GetSamenessGroupFailoverTargets(name string, entMeta acl.EnterpriseMeta) ([]structs.QueryFailoverTarget, error) {
|
|
|
|
m.sl = mockStateLookup{
|
|
|
|
SamenessGroup: m.SamenessGroup,
|
|
|
|
}
|
|
|
|
return m.queryServerWrapper.GetSamenessGroupFailoverTargets(name, entMeta)
|
|
|
|
}
|
|
|
|
|
2015-11-11 05:16:04 +00:00
|
|
|
func TestPreparedQuery_queryFailover(t *testing.T) {
|
2017-06-27 13:22:18 +00:00
|
|
|
t.Parallel()
|
2023-03-22 13:24:13 +00:00
|
|
|
query := structs.PreparedQuery{
|
2016-12-10 19:19:08 +00:00
|
|
|
Name: "test",
|
2015-11-11 05:16:04 +00:00
|
|
|
Service: structs.ServiceQuery{
|
2022-07-22 13:14:43 +00:00
|
|
|
Failover: structs.QueryFailoverOptions{
|
2015-11-11 05:16:04 +00:00
|
|
|
NearestN: 0,
|
|
|
|
Datacenters: []string{""},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes := func() structs.CheckServiceNodes {
|
|
|
|
return structs.CheckServiceNodes{
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node1"},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node2"},
|
|
|
|
},
|
|
|
|
structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node3"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Datacenters are available but the query doesn't use them.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("Query no datacenters used", func(t *testing.T) {
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 0 || reply.Datacenter != "" || reply.Failovers != 0 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Make it fail to get datacenters.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("Fail to get datacenters", func(t *testing.T) {
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
|
|
|
DatacentersError: fmt.Errorf("XXX"),
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply)
|
2015-11-11 05:16:04 +00:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "XXX") {
|
|
|
|
t.Fatalf("bad: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 0 || reply.Datacenter != "" || reply.Failovers != 0 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// The query wants to use other datacenters but none are available.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("no datacenters available", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 3
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 0 || reply.Datacenter != "" || reply.Failovers != 0 {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Try the first three nearest datacenters, first one has the data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("first datacenter has data", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 3
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc1" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc1" || reply.Failovers != 1 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Try the first three nearest datacenters, last one has the data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("last datacenter has data", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 3
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc3" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc3" || reply.Failovers != 3 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc3:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Try the first four nearest datacenters, nobody has the data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("no datacenters with data", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 4
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 0 ||
|
|
|
|
reply.Datacenter != "xxx" || reply.Failovers != 4 {
|
2022-07-22 13:14:43 +00:00
|
|
|
t.Fatalf("bad: %+v", reply)
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc3:PreparedQuery.ExecuteRemote|xxx:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Try the first two nearest datacenters, plus a user-specified one that
|
|
|
|
// has the data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("user specified datacenter with data", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 2
|
|
|
|
query.Service.Failover.Datacenters = []string{"dc4"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc4" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc4" || reply.Failovers != 3 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc4:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Add in a hard-coded value that overlaps with the nearest list.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("overlap with nearest list", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 2
|
|
|
|
query.Service.Failover.Datacenters = []string{"dc4", "dc1"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc4" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc4" || reply.Failovers != 3 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc4:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Now add a bogus user-defined one to the mix.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("bogus user-defined", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 2
|
|
|
|
query.Service.Failover.Datacenters = []string{"nope", "dc4", "dc1"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc4" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc4" || reply.Failovers != 3 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc4:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2022-02-03 22:07:39 +00:00
|
|
|
require.Contains(t, mock.LogBuffer.String(), "Skipping unknown datacenter")
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Same setup as before but dc1 is going to return an error and should
|
|
|
|
// get skipped over, still yielding data from dc4 which comes later.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("dc1 error", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 2
|
|
|
|
query.Service.Failover.Datacenters = []string{"dc4", "dc1"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "dc1" {
|
2015-11-11 05:16:04 +00:00
|
|
|
return fmt.Errorf("XXX")
|
2022-07-22 13:14:43 +00:00
|
|
|
} else if req.Datacenter == "dc4" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "dc4" || reply.Failovers != 3 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc1:PreparedQuery.ExecuteRemote|dc2:PreparedQuery.ExecuteRemote|dc4:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
|
|
|
if !strings.Contains(mock.LogBuffer.String(), "Failed querying") {
|
|
|
|
t.Fatalf("bad: %s", mock.LogBuffer.String())
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Just use a hard-coded list and now xxx has the data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("hard coded list", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 0
|
|
|
|
query.Service.Failover.Datacenters = []string{"dc3", "xxx"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "xxx" {
|
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "xxx" || reply.Failovers != 2 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "dc3:PreparedQuery.ExecuteRemote|xxx:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2015-11-11 05:16:04 +00:00
|
|
|
|
|
|
|
// Make sure the limit and query options are plumbed through.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("limit and query options used", func(t *testing.T) {
|
|
|
|
query.Service.Failover.NearestN = 0
|
|
|
|
query.Service.Failover.Datacenters = []string{"xxx"}
|
2015-11-11 05:16:04 +00:00
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc1", "dc2", "dc3", "xxx", "dc4"},
|
2022-07-22 13:14:43 +00:00
|
|
|
QueryFn: func(req *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if req.Datacenter == "xxx" {
|
|
|
|
if req.Limit != 5 {
|
|
|
|
t.Fatalf("bad: %d", req.Limit)
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
2022-07-22 13:14:43 +00:00
|
|
|
if req.RequireConsistent != true {
|
|
|
|
t.Fatalf("bad: %v", req.RequireConsistent)
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
2022-07-22 13:14:43 +00:00
|
|
|
reply.Nodes = nodes()
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
2018-06-05 21:42:01 +00:00
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{
|
|
|
|
Limit: 5,
|
|
|
|
QueryOptions: structs.QueryOptions{RequireConsistent: true},
|
|
|
|
}, &reply); err != nil {
|
2015-11-11 05:16:04 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if len(reply.Nodes) != 3 ||
|
|
|
|
reply.Datacenter != "xxx" || reply.Failovers != 1 ||
|
|
|
|
!reflect.DeepEqual(reply.Nodes, nodes()) {
|
|
|
|
t.Fatalf("bad: %v", reply)
|
|
|
|
}
|
|
|
|
if queries := mock.JoinQueryLog(); queries != "xxx:PreparedQuery.ExecuteRemote" {
|
|
|
|
t.Fatalf("bad: %s", queries)
|
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
2022-07-22 13:14:43 +00:00
|
|
|
|
|
|
|
// Failover returns data from the first cluster peer with data.
|
2023-03-27 22:40:49 +00:00
|
|
|
t.Run("failover first peer with data", func(t *testing.T) {
|
|
|
|
query.Service.Failover.Datacenters = nil
|
|
|
|
query.Service.Failover.Targets = []structs.QueryFailoverTarget{
|
|
|
|
{Peer: "cluster-01"},
|
|
|
|
{Datacenter: "dc44"},
|
|
|
|
{Peer: "cluster-02"},
|
|
|
|
}
|
|
|
|
{
|
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: []string{"dc44"},
|
|
|
|
QueryFn: func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if args.Query.Service.Peer == "cluster-02" {
|
|
|
|
reply.Nodes = nodes()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
require.Equal(t, "cluster-02", reply.PeerName)
|
|
|
|
require.Equal(t, 3, reply.Failovers)
|
|
|
|
require.Equal(t, nodes(), reply.Nodes)
|
|
|
|
require.Equal(t, "peer:cluster-01|dc44:PreparedQuery.ExecuteRemote|peer:cluster-02", mock.JoinQueryLog())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
targets []structs.QueryFailoverTarget
|
|
|
|
datacenters []string
|
|
|
|
queryfn func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error
|
|
|
|
expectedPeer string
|
|
|
|
expectedDatacenter string
|
|
|
|
expectedReplies int
|
|
|
|
expectedQuery string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "failover first peer with data",
|
|
|
|
targets: []structs.QueryFailoverTarget{
|
|
|
|
{Peer: "cluster-01"},
|
|
|
|
{Datacenter: "dc44"},
|
|
|
|
{Peer: "cluster-02"},
|
|
|
|
},
|
|
|
|
queryfn: func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
2022-10-04 18:46:15 +00:00
|
|
|
if args.Query.Service.Peer == "cluster-02" {
|
2022-07-22 13:14:43 +00:00
|
|
|
reply.Nodes = nodes()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
2023-03-27 22:40:49 +00:00
|
|
|
datacenters: []string{"dc44"},
|
|
|
|
expectedPeer: "cluster-02",
|
|
|
|
expectedDatacenter: "",
|
|
|
|
expectedReplies: 3,
|
|
|
|
expectedQuery: "peer:cluster-01|dc44:PreparedQuery.ExecuteRemote|peer:cluster-02",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "failover datacenter with data",
|
|
|
|
targets: []structs.QueryFailoverTarget{
|
|
|
|
{Peer: "cluster-01"},
|
|
|
|
{Datacenter: "dc44"},
|
|
|
|
{Peer: "cluster-02"},
|
|
|
|
},
|
|
|
|
queryfn: func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error {
|
|
|
|
if args.Datacenter == "dc44" {
|
|
|
|
reply.Nodes = nodes()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
datacenters: []string{"dc44"},
|
|
|
|
expectedPeer: "",
|
|
|
|
expectedDatacenter: "dc44",
|
|
|
|
expectedReplies: 2,
|
|
|
|
expectedQuery: "peer:cluster-01|dc44:PreparedQuery.ExecuteRemote",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
query.Service.Failover.Datacenters = nil
|
|
|
|
query.Service.Failover.Targets = tt.targets
|
|
|
|
|
|
|
|
mock := &mockQueryServer{
|
|
|
|
Datacenters: tt.datacenters,
|
|
|
|
QueryFn: tt.queryfn,
|
|
|
|
}
|
|
|
|
|
|
|
|
var reply structs.PreparedQueryExecuteResponse
|
|
|
|
if err := queryFailover(mock, query, &structs.PreparedQueryExecuteRequest{}, &reply); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
require.Equal(t, tt.expectedPeer, reply.PeerName)
|
|
|
|
require.Equal(t, tt.expectedReplies, reply.Failovers)
|
|
|
|
require.Equal(t, nodes(), reply.Nodes)
|
|
|
|
require.Equal(t, tt.expectedQuery, mock.JoinQueryLog())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type serverTestMetadata struct {
|
|
|
|
server *Server
|
|
|
|
codec rpc.ClientCodec
|
|
|
|
datacenter string
|
|
|
|
acceptingPeerName string
|
|
|
|
dialingPeerName string
|
|
|
|
}
|
|
|
|
|
|
|
|
type executeServers struct {
|
|
|
|
server *serverTestMetadata
|
|
|
|
peeringServer *serverTestMetadata
|
|
|
|
wanServer *serverTestMetadata
|
|
|
|
execToken string
|
|
|
|
denyToken string
|
|
|
|
execNoNodesToken string
|
|
|
|
}
|
|
|
|
|
|
|
|
func createExecuteServers(t *testing.T) *executeServers {
|
|
|
|
es := newExecuteServers(t)
|
|
|
|
es.initWanFed(t)
|
|
|
|
es.exportPeeringServices(t)
|
|
|
|
es.initTokens(t)
|
|
|
|
|
|
|
|
return es
|
|
|
|
}
|
|
|
|
|
|
|
|
func newExecuteServers(t *testing.T) *executeServers {
|
|
|
|
|
|
|
|
// Setup server
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
|
|
c.PrimaryDatacenter = "dc1"
|
|
|
|
c.ACLsEnabled = true
|
|
|
|
c.ACLInitialManagementToken = "root"
|
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
os.RemoveAll(dir1)
|
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
s1.Shutdown()
|
|
|
|
})
|
|
|
|
waitForLeaderEstablishment(t, s1)
|
|
|
|
codec1 := rpcClient(t, s1)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
codec1.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
ca := connect.TestCA(t, nil)
|
|
|
|
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
|
|
|
c.Datacenter = "dc3"
|
|
|
|
c.PrimaryDatacenter = "dc3"
|
|
|
|
c.NodeName = "acceptingServer.dc3"
|
|
|
|
c.GRPCTLSPort = freeport.GetOne(t)
|
|
|
|
c.CAConfig = &structs.CAConfiguration{
|
|
|
|
ClusterID: connect.TestClusterID,
|
|
|
|
Provider: structs.ConsulCAProvider,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"PrivateKey": ca.SigningKey,
|
|
|
|
"RootCert": ca.RootCert,
|
|
|
|
},
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
os.RemoveAll(dir3)
|
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
s3.Shutdown()
|
|
|
|
})
|
|
|
|
waitForLeaderEstablishment(t, s3)
|
|
|
|
codec3 := rpcClient(t, s3)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
codec3.Close()
|
|
|
|
})
|
2022-07-22 13:14:43 +00:00
|
|
|
|
2023-03-27 22:40:49 +00:00
|
|
|
// check for RPC forwarding
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1", testrpc.WithToken("root"))
|
|
|
|
testrpc.WaitForLeader(t, s3.RPC, "dc3")
|
|
|
|
|
|
|
|
acceptingPeerName := "my-peer-accepting-server"
|
|
|
|
dialingPeerName := "my-peer-dialing-server"
|
|
|
|
|
|
|
|
// Set up peering between dc1 (dialing) and dc3 (accepting) and export the foo service
|
|
|
|
{
|
|
|
|
// Create a peering by generating a token.
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
|
|
|
|
options := structs.QueryOptions{Token: "root"}
|
|
|
|
ctx, err := grpcexternal.ContextWithQueryOptions(ctx, options)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
conn, err := grpc.DialContext(ctx, s3.config.RPCAddr.String(),
|
|
|
|
grpc.WithContextDialer(newServerDialer(s3.config.RPCAddr.String())),
|
|
|
|
//nolint:staticcheck
|
|
|
|
grpc.WithInsecure(),
|
|
|
|
grpc.WithBlock())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
conn.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
peeringClient := pbpeering.NewPeeringServiceClient(conn)
|
|
|
|
req := pbpeering.GenerateTokenRequest{
|
|
|
|
PeerName: dialingPeerName,
|
|
|
|
}
|
|
|
|
resp, err := peeringClient.GenerateToken(ctx, &req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
conn, err = grpc.DialContext(ctx, s1.config.RPCAddr.String(),
|
|
|
|
grpc.WithContextDialer(newServerDialer(s1.config.RPCAddr.String())),
|
|
|
|
//nolint:staticcheck
|
|
|
|
grpc.WithInsecure(),
|
|
|
|
grpc.WithBlock())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
conn.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
peeringClient = pbpeering.NewPeeringServiceClient(conn)
|
|
|
|
establishReq := pbpeering.EstablishRequest{
|
|
|
|
PeerName: acceptingPeerName,
|
|
|
|
PeeringToken: resp.PeeringToken,
|
|
|
|
}
|
|
|
|
establishResp, err := peeringClient.Establish(ctx, &establishReq)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, establishResp)
|
|
|
|
|
|
|
|
readResp, err := peeringClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: acceptingPeerName})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, readResp)
|
|
|
|
|
|
|
|
// Wait for the stream to be connected.
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
status, found := s1.peerStreamServer.StreamStatus(readResp.GetPeering().GetID())
|
|
|
|
require.True(r, found)
|
|
|
|
require.True(r, status.Connected)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
es := executeServers{
|
|
|
|
server: &serverTestMetadata{
|
|
|
|
server: s1,
|
|
|
|
codec: codec1,
|
|
|
|
datacenter: "dc1",
|
|
|
|
},
|
|
|
|
peeringServer: &serverTestMetadata{
|
|
|
|
server: s3,
|
|
|
|
codec: codec3,
|
|
|
|
datacenter: "dc3",
|
|
|
|
dialingPeerName: dialingPeerName,
|
|
|
|
acceptingPeerName: acceptingPeerName,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return &es
|
|
|
|
}
|
|
|
|
|
|
|
|
func (es *executeServers) initTokens(t *testing.T) {
|
|
|
|
es.execNoNodesToken = createTokenWithPolicyName(t, es.server.codec, "no-nodes", `service_prefix "foo" { policy = "read" }`, "root")
|
|
|
|
rules := `
|
|
|
|
service_prefix "" { policy = "read" }
|
|
|
|
node_prefix "" { policy = "read" }
|
|
|
|
`
|
|
|
|
es.execToken = createTokenWithPolicyName(t, es.server.codec, "with-read", rules, "root")
|
|
|
|
es.denyToken = createTokenWithPolicyName(t, es.server.codec, "with-deny", `service_prefix "foo" { policy = "deny" }`, "root")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (es *executeServers) exportPeeringServices(t *testing.T) {
|
|
|
|
exportedServices := structs.ConfigEntryRequest{
|
|
|
|
Op: structs.ConfigEntryUpsert,
|
|
|
|
Datacenter: "dc3",
|
|
|
|
Entry: &structs.ExportedServicesConfigEntry{
|
|
|
|
Name: "default",
|
|
|
|
Services: []structs.ExportedService{
|
|
|
|
{
|
|
|
|
Name: "foo",
|
|
|
|
Consumers: []structs.ServiceConsumer{{Peer: es.peeringServer.dialingPeerName}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
var configOutput bool
|
|
|
|
require.NoError(t, msgpackrpc.CallWithCodec(es.peeringServer.codec, "ConfigEntry.Apply", &exportedServices, &configOutput))
|
|
|
|
require.True(t, configOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (es *executeServers) initWanFed(t *testing.T) {
|
|
|
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
|
|
c.Datacenter = "dc2"
|
|
|
|
c.PrimaryDatacenter = "dc1"
|
|
|
|
c.ACLsEnabled = true
|
|
|
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
os.RemoveAll(dir2)
|
|
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
s2.Shutdown()
|
|
|
|
})
|
|
|
|
waitForLeaderEstablishment(t, s2)
|
|
|
|
codec2 := rpcClient(t, s2)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
codec2.Close()
|
|
|
|
})
|
|
|
|
|
|
|
|
s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig)
|
|
|
|
|
|
|
|
// Try to WAN join.
|
|
|
|
joinWAN(t, s2, es.server.server)
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
if got, want := len(es.server.server.WANMembers()), 2; got != want {
|
|
|
|
r.Fatalf("got %d WAN members want %d", got, want)
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
2023-03-27 22:40:49 +00:00
|
|
|
if got, want := len(s2.WANMembers()), 2; got != want {
|
|
|
|
r.Fatalf("got %d WAN members want %d", got, want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
testrpc.WaitForLeader(t, es.server.server.RPC, "dc2", testrpc.WithToken("root"))
|
|
|
|
es.wanServer = &serverTestMetadata{
|
|
|
|
server: s2,
|
|
|
|
codec: codec2,
|
|
|
|
datacenter: "dc2",
|
2022-07-22 13:14:43 +00:00
|
|
|
}
|
2015-11-11 05:16:04 +00:00
|
|
|
}
|