795 lines
24 KiB
Go
795 lines
24 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package nomad
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
|
"github.com/hashicorp/nomad/acl"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/testutil"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNamespaceEndpoint_GetNamespace(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns := mock.Namespace()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns})
|
|
|
|
// Lookup the namespace
|
|
get := &structs.NamespaceSpecificRequest{
|
|
Name: ns.Name,
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
var resp structs.SingleNamespaceResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Equal(ns, resp.Namespace)
|
|
|
|
// Lookup non-existing namespace
|
|
get.Name = uuid.Generate()
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Nil(resp.Namespace)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_GetNamespace_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
state := s1.fsm.State()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Create the policy and tokens
|
|
validToken := mock.CreatePolicyAndToken(t, state, 1002, "test-valid",
|
|
mock.NamespacePolicy(ns1.Name, "", []string{acl.NamespaceCapabilityReadJob}))
|
|
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
|
mock.NamespacePolicy(ns2.Name, "", []string{acl.NamespaceCapabilityReadJob}))
|
|
|
|
get := &structs.NamespaceSpecificRequest{
|
|
Name: ns1.Name,
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
|
|
// Lookup the namespace without a token and expect failure
|
|
{
|
|
var resp structs.SingleNamespaceResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
}
|
|
|
|
// Try with an invalid token
|
|
get.AuthToken = invalidToken.SecretID
|
|
{
|
|
var resp structs.SingleNamespaceResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
}
|
|
|
|
// Try with a valid token
|
|
get.AuthToken = validToken.SecretID
|
|
{
|
|
var resp structs.SingleNamespaceResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Equal(ns1, resp.Namespace)
|
|
}
|
|
|
|
// Try with a root token
|
|
get.AuthToken = root.SecretID
|
|
{
|
|
var resp structs.SingleNamespaceResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Equal(ns1, resp.Namespace)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_GetNamespace_Blocking(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
state := s1.fsm.State()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the namespaces
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
|
|
// First create an namespace
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.UpsertNamespaces(100, []*structs.Namespace{ns1}))
|
|
})
|
|
|
|
// Upsert the namespace we are watching later
|
|
time.AfterFunc(200*time.Millisecond, func() {
|
|
assert.Nil(state.UpsertNamespaces(200, []*structs.Namespace{ns2}))
|
|
})
|
|
|
|
// Lookup the namespace
|
|
req := &structs.NamespaceSpecificRequest{
|
|
Name: ns2.Name,
|
|
QueryOptions: structs.QueryOptions{
|
|
Region: "global",
|
|
MinQueryIndex: 150,
|
|
},
|
|
}
|
|
var resp structs.SingleNamespaceResponse
|
|
start := time.Now()
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", req, &resp))
|
|
assert.EqualValues(200, resp.Index)
|
|
assert.NotNil(resp.Namespace)
|
|
assert.Equal(ns2.Name, resp.Namespace.Name)
|
|
|
|
if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
|
|
}
|
|
|
|
// Namespace delete triggers watches
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.DeleteNamespaces(300, []string{ns2.Name}))
|
|
})
|
|
|
|
req.QueryOptions.MinQueryIndex = 250
|
|
var resp2 structs.SingleNamespaceResponse
|
|
start = time.Now()
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespace", req, &resp2))
|
|
assert.EqualValues(300, resp2.Index)
|
|
assert.Nil(resp2.Namespace)
|
|
|
|
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_GetNamespaces(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Lookup the namespace
|
|
get := &structs.NamespaceSetRequest{
|
|
Namespaces: []string{ns1.Name, ns2.Name},
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
var resp structs.NamespaceSetResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 2)
|
|
assert.Contains(resp.Namespaces, ns1.Name)
|
|
assert.Contains(resp.Namespaces, ns2.Name)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_GetNamespaces_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
state := s1.fsm.State()
|
|
state.UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Create the policy and tokens
|
|
validToken := mock.CreatePolicyAndToken(t, state, 1002, "test-valid",
|
|
mock.NamespacePolicy(ns1.Name, "", []string{acl.NamespaceCapabilityReadJob}))
|
|
|
|
// Lookup the namespace
|
|
get := &structs.NamespaceSetRequest{
|
|
Namespaces: []string{ns1.Name, ns2.Name},
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
|
|
// Lookup the namespaces without a token and expect a failure
|
|
{
|
|
var resp structs.NamespaceSetResponse
|
|
assert.NotNil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", get, &resp))
|
|
}
|
|
|
|
// Try with an non-management token
|
|
get.AuthToken = validToken.SecretID
|
|
{
|
|
var resp structs.NamespaceSetResponse
|
|
assert.NotNil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", get, &resp))
|
|
}
|
|
|
|
// Try with a root token
|
|
get.AuthToken = root.SecretID
|
|
{
|
|
var resp structs.NamespaceSetResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 2)
|
|
assert.Contains(resp.Namespaces, ns1.Name)
|
|
assert.Contains(resp.Namespaces, ns2.Name)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_GetNamespaces_Blocking(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
state := s1.fsm.State()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the namespaces
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
|
|
// First create an namespace
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.UpsertNamespaces(100, []*structs.Namespace{ns1}))
|
|
})
|
|
|
|
// Upsert the namespace we are watching later
|
|
time.AfterFunc(200*time.Millisecond, func() {
|
|
assert.Nil(state.UpsertNamespaces(200, []*structs.Namespace{ns2}))
|
|
})
|
|
|
|
// Lookup the namespace
|
|
req := &structs.NamespaceSetRequest{
|
|
Namespaces: []string{ns2.Name},
|
|
QueryOptions: structs.QueryOptions{
|
|
Region: "global",
|
|
MinQueryIndex: 150,
|
|
},
|
|
}
|
|
var resp structs.NamespaceSetResponse
|
|
start := time.Now()
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", req, &resp))
|
|
assert.EqualValues(200, resp.Index)
|
|
assert.Len(resp.Namespaces, 1)
|
|
assert.Contains(resp.Namespaces, ns2.Name)
|
|
|
|
if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
|
|
}
|
|
|
|
// Namespace delete triggers watches
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.DeleteNamespaces(300, []string{ns2.Name}))
|
|
})
|
|
|
|
req.QueryOptions.MinQueryIndex = 250
|
|
var resp2 structs.NamespaceSetResponse
|
|
start = time.Now()
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.GetNamespaces", req, &resp2))
|
|
assert.EqualValues(300, resp2.Index)
|
|
assert.Empty(resp2.Namespaces)
|
|
|
|
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_List(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
|
|
ns1.Name = "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"
|
|
ns2.Name = "aaaabbbb-3350-4b4b-d185-0e1992ed43e9"
|
|
assert.Nil(s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2}))
|
|
|
|
// Lookup the namespaces
|
|
get := &structs.NamespaceListRequest{
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
var resp structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 3)
|
|
|
|
// Lookup the namespaces by prefix
|
|
get = &structs.NamespaceListRequest{
|
|
QueryOptions: structs.QueryOptions{
|
|
Region: "global",
|
|
Prefix: "aaaabb",
|
|
},
|
|
}
|
|
var resp2 structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp2))
|
|
assert.EqualValues(1000, resp2.Index)
|
|
assert.Len(resp2.Namespaces, 1)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_List_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
state := s1.fsm.State()
|
|
|
|
ns1.Name = "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"
|
|
ns2.Name = "bbbbbbbb-3350-4b4b-d185-0e1992ed43e9"
|
|
assert.Nil(s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2}))
|
|
|
|
validDefToken := mock.CreatePolicyAndToken(t, state, 1001, "test-def-valid",
|
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadFS}))
|
|
validMultiToken := mock.CreatePolicyAndToken(t, state, 1002, "test-multi-valid", fmt.Sprintf("%s\n%s",
|
|
mock.NamespacePolicy(ns1.Name, "", []string{acl.NamespaceCapabilityReadJob}),
|
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})))
|
|
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
|
mock.NamespacePolicy("invalid-namespace", "", []string{acl.NamespaceCapabilityReadJob}))
|
|
|
|
get := &structs.NamespaceListRequest{
|
|
QueryOptions: structs.QueryOptions{Region: "global"},
|
|
}
|
|
|
|
// Lookup the namespaces without a token and expect a failure
|
|
{
|
|
var resp structs.NamespaceListResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp)
|
|
assert.Nil(err)
|
|
assert.Len(resp.Namespaces, 0)
|
|
}
|
|
|
|
// Try with an invalid token
|
|
get.AuthToken = invalidToken.SecretID
|
|
{
|
|
var resp structs.NamespaceListResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp)
|
|
assert.Nil(err)
|
|
assert.Len(resp.Namespaces, 0)
|
|
}
|
|
|
|
// Try with a valid token for one
|
|
get.AuthToken = validDefToken.SecretID
|
|
{
|
|
var resp structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 1)
|
|
}
|
|
|
|
// Try with a valid token for two
|
|
get.AuthToken = validMultiToken.SecretID
|
|
{
|
|
var resp structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 2)
|
|
}
|
|
|
|
// Try with a root token
|
|
get.AuthToken = root.SecretID
|
|
{
|
|
var resp structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", get, &resp))
|
|
assert.EqualValues(1000, resp.Index)
|
|
assert.Len(resp.Namespaces, 3)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_List_Blocking(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
state := s1.fsm.State()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the namespace
|
|
ns := mock.Namespace()
|
|
|
|
// Upsert namespace triggers watches
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.UpsertNamespaces(200, []*structs.Namespace{ns}))
|
|
})
|
|
|
|
req := &structs.NamespaceListRequest{
|
|
QueryOptions: structs.QueryOptions{
|
|
Region: "global",
|
|
MinQueryIndex: 150,
|
|
},
|
|
}
|
|
start := time.Now()
|
|
var resp structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", req, &resp))
|
|
|
|
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
|
|
}
|
|
assert.EqualValues(200, resp.Index)
|
|
assert.Len(resp.Namespaces, 2)
|
|
|
|
// Namespace deletion triggers watches
|
|
time.AfterFunc(100*time.Millisecond, func() {
|
|
assert.Nil(state.DeleteNamespaces(300, []string{ns.Name}))
|
|
})
|
|
|
|
req.MinQueryIndex = 200
|
|
start = time.Now()
|
|
var resp2 structs.NamespaceListResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.ListNamespaces", req, &resp2))
|
|
|
|
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
|
t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
|
|
}
|
|
assert.EqualValues(300, resp2.Index)
|
|
assert.Len(resp2.Namespaces, 1)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_DeleteNamespaces(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Lookup the namespaces
|
|
req := &structs.NamespaceDeleteRequest{
|
|
Namespaces: []string{ns1.Name, ns2.Name},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
var resp structs.GenericResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp))
|
|
assert.NotEqual(uint64(0), resp.Index)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_DeleteNamespaces_NonTerminal_Local(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Create a job in one
|
|
j := mock.Job()
|
|
j.Namespace = ns1.Name
|
|
assert.Nil(s1.fsm.State().UpsertJob(structs.MsgTypeTestSetup, 1001, nil, j))
|
|
|
|
// Lookup the namespaces
|
|
req := &structs.NamespaceDeleteRequest{
|
|
Namespaces: []string{ns1.Name, ns2.Name},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp)
|
|
if assert.NotNil(err) {
|
|
assert.Contains(err.Error(), "has non-terminal jobs")
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_DeleteNamespaces_NonTerminal_Federated_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, func(c *Config) {
|
|
c.Region = "region1"
|
|
c.AuthoritativeRegion = "region1"
|
|
c.ACLEnabled = true
|
|
})
|
|
defer cleanupS1()
|
|
s2, _, cleanupS2 := TestACLServer(t, func(c *Config) {
|
|
c.Region = "region2"
|
|
c.AuthoritativeRegion = "region1"
|
|
c.ACLEnabled = true
|
|
c.ReplicationBackoff = 20 * time.Millisecond
|
|
c.ReplicationToken = root.SecretID
|
|
})
|
|
defer cleanupS2()
|
|
TestJoin(t, s1, s2)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
testutil.WaitForLeader(t, s2.RPC)
|
|
codec := rpcClient(t, s1)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1})
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
state := s2.State()
|
|
out, err := state.NamespaceByName(nil, ns1.Name)
|
|
return out != nil, err
|
|
}, func(err error) {
|
|
t.Fatalf("should replicate namespace")
|
|
})
|
|
|
|
// Create a job in the namespace on the non-authority
|
|
j := mock.Job()
|
|
j.Namespace = ns1.Name
|
|
assert.Nil(s2.fsm.State().UpsertJob(structs.MsgTypeTestSetup, 1001, nil, j))
|
|
|
|
// Delete the namespaces without the correct permissions
|
|
req := &structs.NamespaceDeleteRequest{
|
|
Namespaces: []string{ns1.Name},
|
|
WriteRequest: structs.WriteRequest{
|
|
Region: "global",
|
|
},
|
|
}
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp)
|
|
if assert.NotNil(err) {
|
|
assert.EqualError(err, structs.ErrPermissionDenied.Error())
|
|
}
|
|
|
|
// Try with a auth token
|
|
req.AuthToken = root.SecretID
|
|
var resp2 structs.GenericResponse
|
|
err = msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp2)
|
|
if assert.NotNil(err) {
|
|
assert.Contains(err.Error(), "has non-terminal jobs")
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_DeleteNamespaces_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
state := s1.fsm.State()
|
|
s1.fsm.State().UpsertNamespaces(1000, []*structs.Namespace{ns1, ns2})
|
|
|
|
// Create the policy and tokens
|
|
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
|
|
|
|
req := &structs.NamespaceDeleteRequest{
|
|
Namespaces: []string{ns1.Name, ns2.Name},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
|
|
// Delete namespaces without a token and expect failure
|
|
{
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
|
|
// Check we did not delete the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
}
|
|
|
|
// Try with an invalid token
|
|
req.AuthToken = invalidToken.SecretID
|
|
{
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
|
|
// Check we did not delete the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
}
|
|
|
|
// Try with a root token
|
|
req.AuthToken = root.SecretID
|
|
{
|
|
var resp structs.GenericResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp))
|
|
assert.NotEqual(uint64(0), resp.Index)
|
|
|
|
// Check we deleted the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
}
|
|
}
|
|
|
|
func TestNamespaceEndpoint_DeleteNamespaces_Default(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Delete the default namespace
|
|
req := &structs.NamespaceDeleteRequest{
|
|
Namespaces: []string{structs.DefaultNamespace},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
var resp structs.GenericResponse
|
|
assert.NotNil(msgpackrpc.CallWithCodec(codec, "Namespace.DeleteNamespaces", req, &resp))
|
|
}
|
|
|
|
func TestNamespaceEndpoint_UpsertNamespaces(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, cleanupS1 := TestServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
// Create the register request
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
|
|
// Lookup the namespaces
|
|
req := &structs.NamespaceUpsertRequest{
|
|
Namespaces: []*structs.Namespace{ns1, ns2},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
var resp structs.GenericResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.UpsertNamespaces", req, &resp))
|
|
assert.NotEqual(uint64(0), resp.Index)
|
|
|
|
// Check we created the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
}
|
|
|
|
func TestNamespaceEndpoint_UpsertNamespaces_ACL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
assert := assert.New(t)
|
|
s1, root, cleanupS1 := TestACLServer(t, nil)
|
|
defer cleanupS1()
|
|
codec := rpcClient(t, s1)
|
|
testutil.WaitForLeader(t, s1.RPC)
|
|
|
|
ns1 := mock.Namespace()
|
|
ns2 := mock.Namespace()
|
|
state := s1.fsm.State()
|
|
|
|
// Create the policy and tokens
|
|
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
|
|
|
|
// Create the register request
|
|
req := &structs.NamespaceUpsertRequest{
|
|
Namespaces: []*structs.Namespace{ns1, ns2},
|
|
WriteRequest: structs.WriteRequest{Region: "global"},
|
|
}
|
|
|
|
// Upsert the namespace without a token and expect failure
|
|
{
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.UpsertNamespaces", req, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
|
|
// Check we did not create the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
}
|
|
|
|
// Try with an invalid token
|
|
req.AuthToken = invalidToken.SecretID
|
|
{
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.UpsertNamespaces", req, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
|
|
// Check we did not create the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
}
|
|
|
|
// Try with a bogus token
|
|
req.AuthToken = uuid.Generate()
|
|
{
|
|
var resp structs.GenericResponse
|
|
err := msgpackrpc.CallWithCodec(codec, "Namespace.UpsertNamespaces", req, &resp)
|
|
assert.NotNil(err)
|
|
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
|
|
// Check we did not create the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.Nil(out)
|
|
}
|
|
|
|
// Try with a root token
|
|
req.AuthToken = root.SecretID
|
|
{
|
|
var resp structs.GenericResponse
|
|
assert.Nil(msgpackrpc.CallWithCodec(codec, "Namespace.UpsertNamespaces", req, &resp))
|
|
assert.NotEqual(uint64(0), resp.Index)
|
|
|
|
// Check we created the namespaces
|
|
out, err := s1.fsm.State().NamespaceByName(nil, ns1.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
|
|
out, err = s1.fsm.State().NamespaceByName(nil, ns2.Name)
|
|
assert.Nil(err)
|
|
assert.NotNil(out)
|
|
}
|
|
}
|