ba728f8f97
* api: enable support for setting original source alongside job This PR adds support for setting job source material along with the registration of a job. This includes a new HTTP endpoint and a new RPC endpoint for making queries for the original source of a job. The HTTP endpoint is /v1/job/<id>/submission?version=<version> and the RPC method is Job.GetJobSubmission. The job source (if submitted, and doing so is always optional), is stored in the job_submission memdb table, separately from the actual job. This way we do not incur overhead of reading the large string field throughout normal job operations. The server config now includes job_max_source_size for configuring the maximum size the job source may be, before the server simply drops the source material. This should help prevent Bad Things from happening when huge jobs are submitted. If the value is set to 0, all job source material will be dropped. * api: avoid writing var content to disk for parsing * api: move submission validation into RPC layer * api: return an error if updating a job submission without namespace or job id * api: be exact about the job index we associate a submission with (modify) * api: reword api docs scheduling * api: prune all but the last 6 job submissions * api: protect against nil job submission in job validation * api: set max job source size in test server * api: fixups from pr
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)
|
|
}
|
|
}
|