Deployment Status Command Does Not Respect -namespace Wildcard (#16792)
* func: add namespace support for list deployment * func: add wildcard to namespace filter for deployments * Update deployment_endpoint.go * style: use must instead of require or asseert * style: rename paginator to avoid clash with import * style: add changelog entry * fix: add missing parameter for upsert jobs
This commit is contained in:
parent
f615bd92e5
commit
8302085384
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
core: the deployment's list endpoint now supports look up by prefix using the wildcard for namespace
|
||||||
|
```
|
|
@ -450,20 +450,33 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
|
||||||
|
|
||||||
// Check namespace read-job permissions against request namespace since
|
// Check namespace read-job permissions against request namespace since
|
||||||
// results are filtered by request namespace.
|
// results are filtered by request namespace.
|
||||||
if aclObj, err := d.srv.ResolveACL(args); err != nil {
|
aclObj, err := d.srv.ResolveACL(args)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
|
}
|
||||||
|
|
||||||
|
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
|
||||||
return structs.ErrPermissionDenied
|
return structs.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allow := aclObj.AllowNsOpFunc(acl.NamespaceCapabilityReadJob)
|
||||||
|
|
||||||
// Setup the blocking query
|
// Setup the blocking query
|
||||||
sort := state.SortOption(args.Reverse)
|
sort := state.SortOption(args.Reverse)
|
||||||
opts := blockingOptions{
|
opts := blockingOptions{
|
||||||
queryOpts: &args.QueryOptions,
|
queryOpts: &args.QueryOptions,
|
||||||
queryMeta: &reply.QueryMeta,
|
queryMeta: &reply.QueryMeta,
|
||||||
run: func(ws memdb.WatchSet, store *state.StateStore) error {
|
run: func(ws memdb.WatchSet, store *state.StateStore) error {
|
||||||
|
allowableNamespaces, err := allowedNSes(aclObj, store, allow)
|
||||||
|
if err != nil {
|
||||||
|
if err == structs.ErrPermissionDenied {
|
||||||
|
reply.Deployments = make([]*structs.Deployment, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Capture all the deployments
|
// Capture all the deployments
|
||||||
var err error
|
|
||||||
var iter memdb.ResultIterator
|
var iter memdb.ResultIterator
|
||||||
var opts paginator.StructsTokenizerOptions
|
var opts paginator.StructsTokenizerOptions
|
||||||
|
|
||||||
|
@ -491,8 +504,14 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
|
||||||
|
|
||||||
tokenizer := paginator.NewStructsTokenizer(iter, opts)
|
tokenizer := paginator.NewStructsTokenizer(iter, opts)
|
||||||
|
|
||||||
|
filters := []paginator.Filter{
|
||||||
|
paginator.NamespaceFilter{
|
||||||
|
AllowableNamespaces: allowableNamespaces,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var deploys []*structs.Deployment
|
var deploys []*structs.Deployment
|
||||||
paginator, err := paginator.NewPaginator(iter, tokenizer, nil, args.QueryOptions,
|
pnator, err := paginator.NewPaginator(iter, tokenizer, filters, args.QueryOptions,
|
||||||
func(raw interface{}) error {
|
func(raw interface{}) error {
|
||||||
deploy := raw.(*structs.Deployment)
|
deploy := raw.(*structs.Deployment)
|
||||||
deploys = append(deploys, deploy)
|
deploys = append(deploys, deploy)
|
||||||
|
@ -503,7 +522,7 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
|
||||||
http.StatusBadRequest, "failed to create result paginator: %v", err)
|
http.StatusBadRequest, "failed to create result paginator: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextToken, err := paginator.Page()
|
nextToken, err := pnator.Page()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return structs.NewErrRPCCodedf(
|
return structs.NewErrRPCCodedf(
|
||||||
http.StatusBadRequest, "failed to read result page: %v", err)
|
http.StatusBadRequest, "failed to read result page: %v", err)
|
||||||
|
@ -522,7 +541,9 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
|
||||||
// Set the query response
|
// Set the query response
|
||||||
d.srv.setQueryMeta(&reply.QueryMeta)
|
d.srv.setQueryMeta(&reply.QueryMeta)
|
||||||
return nil
|
return nil
|
||||||
}}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return d.srv.blockingRPC(&opts)
|
return d.srv.blockingRPC(&opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -974,7 +975,6 @@ func TestDeploymentEndpoint_List(t *testing.T) {
|
||||||
defer cleanupS1()
|
defer cleanupS1()
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
testutil.WaitForLeader(t, s1.RPC)
|
testutil.WaitForLeader(t, s1.RPC)
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// Create the register request
|
// Create the register request
|
||||||
j := mock.Job()
|
j := mock.Job()
|
||||||
|
@ -982,8 +982,8 @@ func TestDeploymentEndpoint_List(t *testing.T) {
|
||||||
d.JobID = j.ID
|
d.JobID = j.ID
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
|
|
||||||
assert.Nil(state.UpsertJob(structs.MsgTypeTestSetup, 999, nil, j), "UpsertJob")
|
must.Nil(t, state.UpsertJob(structs.MsgTypeTestSetup, 999, nil, j), must.Sprint("UpsertJob"))
|
||||||
assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
|
must.Nil(t, state.UpsertDeployment(1000, d), must.Sprint("UpsertDeployment"))
|
||||||
|
|
||||||
// Lookup the deployments
|
// Lookup the deployments
|
||||||
get := &structs.DeploymentListRequest{
|
get := &structs.DeploymentListRequest{
|
||||||
|
@ -993,10 +993,10 @@ func TestDeploymentEndpoint_List(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), must.Sprint("RPC"))
|
||||||
assert.EqualValues(resp.Index, 1000, "Wrong Index")
|
must.Eq(t, resp.Index, 1000, must.Sprint("Wrong Index"))
|
||||||
assert.Len(resp.Deployments, 1, "Deployments")
|
must.Len(t, 1, resp.Deployments, must.Sprint("Deployments"))
|
||||||
assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
|
must.StrContains(t, resp.Deployments[0].ID, d.ID, must.Sprint("Deployment ID"))
|
||||||
|
|
||||||
// Lookup the deploys by prefix
|
// Lookup the deploys by prefix
|
||||||
get = &structs.DeploymentListRequest{
|
get = &structs.DeploymentListRequest{
|
||||||
|
@ -1008,21 +1008,20 @@ func TestDeploymentEndpoint_List(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp2 structs.DeploymentListResponse
|
var resp2 structs.DeploymentListResponse
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp2), "RPC")
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp2), must.Sprint("RPC"))
|
||||||
assert.EqualValues(resp.Index, 1000, "Wrong Index")
|
must.Eq(t, resp.Index, 1000, must.Sprint("Wrong Index"))
|
||||||
assert.Len(resp2.Deployments, 1, "Deployments")
|
must.Len(t, 1, resp2.Deployments, must.Sprint("Deployments"))
|
||||||
assert.Equal(resp2.Deployments[0].ID, d.ID, "Deployment ID")
|
must.Eq(t, resp2.Deployments[0].ID, d.ID, must.Sprint("Deployment ID"))
|
||||||
|
|
||||||
// add another deployment in another namespace
|
// add another deployment in another namespace
|
||||||
|
|
||||||
j2 := mock.Job()
|
j2 := mock.Job()
|
||||||
d2 := mock.Deployment()
|
d2 := mock.Deployment()
|
||||||
j2.Namespace = "prod"
|
j2.Namespace = "prod"
|
||||||
d2.Namespace = "prod"
|
d2.Namespace = "prod"
|
||||||
d2.JobID = j2.ID
|
d2.JobID = j2.ID
|
||||||
assert.Nil(state.UpsertNamespaces(1001, []*structs.Namespace{{Name: "prod"}}))
|
must.Nil(t, state.UpsertNamespaces(1001, []*structs.Namespace{{Name: "prod"}}))
|
||||||
assert.Nil(state.UpsertJob(structs.MsgTypeTestSetup, 1002, nil, j2), "UpsertJob")
|
must.Nil(t, state.UpsertJob(structs.MsgTypeTestSetup, 1002, nil, j2), must.Sprint("UpsertJob"))
|
||||||
assert.Nil(state.UpsertDeployment(1003, d2), "UpsertDeployment")
|
must.Nil(t, state.UpsertDeployment(1003, d2), must.Sprint("UpsertDeployment"))
|
||||||
|
|
||||||
// Lookup the deployments with wildcard namespace
|
// Lookup the deployments with wildcard namespace
|
||||||
get = &structs.DeploymentListRequest{
|
get = &structs.DeploymentListRequest{
|
||||||
|
@ -1031,9 +1030,40 @@ func TestDeploymentEndpoint_List(t *testing.T) {
|
||||||
Namespace: structs.AllNamespacesSentinel,
|
Namespace: structs.AllNamespacesSentinel,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), must.Sprint("RPC"))
|
||||||
assert.EqualValues(resp.Index, 1003, "Wrong Index")
|
must.Eq(t, resp.Index, 1003, must.Sprint("Wrong Index"))
|
||||||
assert.Len(resp.Deployments, 2, "Deployments")
|
must.Len(t, 2, resp.Deployments, must.Sprint("Deployments"))
|
||||||
|
|
||||||
|
// Lookup a deployment with wildcard namespace and prefix
|
||||||
|
var resp3 structs.DeploymentListResponse
|
||||||
|
get = &structs.DeploymentListRequest{
|
||||||
|
QueryOptions: structs.QueryOptions{
|
||||||
|
Region: "global",
|
||||||
|
Prefix: d.ID[:4],
|
||||||
|
Namespace: structs.AllNamespacesSentinel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp3), must.Sprint("RPC"))
|
||||||
|
must.Eq(t, resp3.Index, 1003, must.Sprint("Wrong Index"))
|
||||||
|
must.Len(t, 1, resp3.Deployments, must.Sprint("Deployments"))
|
||||||
|
must.StrContains(t, resp3.Deployments[0].ID, d.ID, must.Sprint("Deployment ID"))
|
||||||
|
|
||||||
|
// Lookup the other deployments with wildcard namespace and prefix
|
||||||
|
var resp4 structs.DeploymentListResponse
|
||||||
|
get = &structs.DeploymentListRequest{
|
||||||
|
QueryOptions: structs.QueryOptions{
|
||||||
|
Region: "global",
|
||||||
|
Prefix: d2.ID[:4],
|
||||||
|
Namespace: structs.AllNamespacesSentinel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp4), must.Sprint("RPC"))
|
||||||
|
must.Eq(t, resp4.Index, 1003, must.Sprint("Wrong Index"))
|
||||||
|
must.Len(t, 1, resp4.Deployments, must.Sprint("Deployments"))
|
||||||
|
must.StrContains(t, resp4.Deployments[0].ID, d2.ID, must.Sprint("Deployment ID"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeploymentEndpoint_List_order(t *testing.T) {
|
func TestDeploymentEndpoint_List_order(t *testing.T) {
|
||||||
|
@ -1058,17 +1088,17 @@ func TestDeploymentEndpoint_List_order(t *testing.T) {
|
||||||
dep3.ID = uuid3
|
dep3.ID = uuid3
|
||||||
|
|
||||||
err := s1.fsm.State().UpsertDeployment(1000, dep1)
|
err := s1.fsm.State().UpsertDeployment(1000, dep1)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
err = s1.fsm.State().UpsertDeployment(1001, dep2)
|
err = s1.fsm.State().UpsertDeployment(1001, dep2)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
err = s1.fsm.State().UpsertDeployment(1002, dep3)
|
err = s1.fsm.State().UpsertDeployment(1002, dep3)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
// update dep2 again so we can later assert create index order did not change
|
// update dep2 again so we can later assert create index order did not change
|
||||||
err = s1.fsm.State().UpsertDeployment(1003, dep2)
|
err = s1.fsm.State().UpsertDeployment(1003, dep2)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
t.Run("default", func(t *testing.T) {
|
||||||
// Lookup the deployments in chronological order (oldest first)
|
// Lookup the deployments in chronological order (oldest first)
|
||||||
|
@ -1081,19 +1111,19 @@ func TestDeploymentEndpoint_List_order(t *testing.T) {
|
||||||
|
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
err = msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
err = msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
require.Equal(t, uint64(1003), resp.Index)
|
must.Eq(t, uint64(1003), resp.Index)
|
||||||
require.Len(t, resp.Deployments, 3)
|
must.Len(t, 3, resp.Deployments)
|
||||||
|
|
||||||
// Assert returned order is by CreateIndex (ascending)
|
// Assert returned order is by CreateIndex (ascending)
|
||||||
require.Equal(t, uint64(1000), resp.Deployments[0].CreateIndex)
|
must.Eq(t, uint64(1000), resp.Deployments[0].CreateIndex)
|
||||||
require.Equal(t, uuid1, resp.Deployments[0].ID)
|
must.Eq(t, uuid1, resp.Deployments[0].ID)
|
||||||
|
|
||||||
require.Equal(t, uint64(1001), resp.Deployments[1].CreateIndex)
|
must.Eq(t, uint64(1001), resp.Deployments[1].CreateIndex)
|
||||||
require.Equal(t, uuid2, resp.Deployments[1].ID)
|
must.Eq(t, uuid2, resp.Deployments[1].ID)
|
||||||
|
|
||||||
require.Equal(t, uint64(1002), resp.Deployments[2].CreateIndex)
|
must.Eq(t, uint64(1002), resp.Deployments[2].CreateIndex)
|
||||||
require.Equal(t, uuid3, resp.Deployments[2].ID)
|
must.Eq(t, uuid3, resp.Deployments[2].ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("reverse", func(t *testing.T) {
|
t.Run("reverse", func(t *testing.T) {
|
||||||
|
@ -1108,19 +1138,19 @@ func TestDeploymentEndpoint_List_order(t *testing.T) {
|
||||||
|
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
err = msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
err = msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
require.Equal(t, uint64(1003), resp.Index)
|
must.Eq(t, uint64(1003), resp.Index)
|
||||||
require.Len(t, resp.Deployments, 3)
|
must.Len(t, 3, resp.Deployments)
|
||||||
|
|
||||||
// Assert returned order is by CreateIndex (descending)
|
// Assert returned order is by CreateIndex (descending)
|
||||||
require.Equal(t, uint64(1002), resp.Deployments[0].CreateIndex)
|
must.Eq(t, uint64(1002), resp.Deployments[0].CreateIndex)
|
||||||
require.Equal(t, uuid3, resp.Deployments[0].ID)
|
must.Eq(t, uuid3, resp.Deployments[0].ID)
|
||||||
|
|
||||||
require.Equal(t, uint64(1001), resp.Deployments[1].CreateIndex)
|
must.Eq(t, uint64(1001), resp.Deployments[1].CreateIndex)
|
||||||
require.Equal(t, uuid2, resp.Deployments[1].ID)
|
must.Eq(t, uuid2, resp.Deployments[1].ID)
|
||||||
|
|
||||||
require.Equal(t, uint64(1000), resp.Deployments[2].CreateIndex)
|
must.Eq(t, uint64(1000), resp.Deployments[2].CreateIndex)
|
||||||
require.Equal(t, uuid1, resp.Deployments[2].ID)
|
must.Eq(t, uuid1, resp.Deployments[2].ID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1131,65 +1161,104 @@ func TestDeploymentEndpoint_List_ACL(t *testing.T) {
|
||||||
defer cleanupS1()
|
defer cleanupS1()
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
testutil.WaitForLeader(t, s1.RPC)
|
testutil.WaitForLeader(t, s1.RPC)
|
||||||
assert := assert.New(t)
|
//assert := assert.New(t)
|
||||||
|
|
||||||
|
// Create dev namespace
|
||||||
|
devNS := mock.Namespace()
|
||||||
|
devNS.Name = "dev"
|
||||||
|
err := s1.fsm.State().UpsertNamespaces(999, []*structs.Namespace{devNS})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create the register request
|
// Create the register request
|
||||||
j := mock.Job()
|
d1 := mock.Deployment()
|
||||||
d := mock.Deployment()
|
d2 := mock.Deployment()
|
||||||
d.JobID = j.ID
|
d2.Namespace = devNS.Name
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
|
|
||||||
assert.Nil(state.UpsertJob(structs.MsgTypeTestSetup, 999, nil, j), "UpsertJob")
|
must.NoError(t, state.UpsertDeployment(1000, d1), must.Sprint("Upsert Deployment failed"))
|
||||||
assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
|
must.NoError(t, state.UpsertDeployment(1001, d2), must.Sprint("Upsert Deployment failed"))
|
||||||
|
|
||||||
// Create the namespace policy and tokens
|
// Create the namespace policy and tokens
|
||||||
validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
|
validToken := mock.CreatePolicyAndToken(t, state, 1002, "test-valid",
|
||||||
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
|
||||||
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
|
||||||
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
|
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
|
||||||
|
devToken := mock.CreatePolicyAndToken(t, state, 1004, "test-dev",
|
||||||
|
mock.NamespacePolicy("dev", "", []string{acl.NamespaceCapabilityReadJob}))
|
||||||
|
|
||||||
get := &structs.DeploymentListRequest{
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
namespace string
|
||||||
|
token string
|
||||||
|
expectedDeployments []string
|
||||||
|
expectedError string
|
||||||
|
prefix string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
token: "",
|
||||||
|
namespace: structs.DefaultNamespace,
|
||||||
|
expectedError: structs.ErrPermissionDenied.Error(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid token",
|
||||||
|
token: invalidToken.SecretID,
|
||||||
|
namespace: structs.DefaultNamespace,
|
||||||
|
expectedError: structs.ErrPermissionDenied.Error(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid token",
|
||||||
|
token: validToken.SecretID,
|
||||||
|
namespace: structs.DefaultNamespace,
|
||||||
|
expectedDeployments: []string{d1.ID},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "root token all namespaces",
|
||||||
|
token: root.SecretID,
|
||||||
|
namespace: structs.AllNamespacesSentinel,
|
||||||
|
expectedDeployments: []string{d1.ID, d2.ID},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "root token default namespace",
|
||||||
|
token: root.SecretID,
|
||||||
|
namespace: structs.DefaultNamespace,
|
||||||
|
expectedDeployments: []string{d1.ID},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dev token all namespaces",
|
||||||
|
token: devToken.SecretID,
|
||||||
|
namespace: structs.AllNamespacesSentinel,
|
||||||
|
expectedDeployments: []string{d2.ID},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
get := &structs.EvalListRequest{
|
||||||
QueryOptions: structs.QueryOptions{
|
QueryOptions: structs.QueryOptions{
|
||||||
|
AuthToken: tc.token,
|
||||||
Region: "global",
|
Region: "global",
|
||||||
Namespace: structs.DefaultNamespace,
|
Namespace: tc.namespace,
|
||||||
|
Prefix: tc.prefix,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try with no token and expect permission denied
|
|
||||||
{
|
|
||||||
var resp structs.DeploymentUpdateResponse
|
|
||||||
err := msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
|
||||||
assert.NotNil(err)
|
|
||||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try with an invalid token
|
|
||||||
{
|
|
||||||
get.AuthToken = invalidToken.SecretID
|
|
||||||
var resp structs.DeploymentUpdateResponse
|
|
||||||
err := msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
|
||||||
assert.NotNil(err)
|
|
||||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup the deployments with a root token
|
|
||||||
{
|
|
||||||
get.AuthToken = root.SecretID
|
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
|
err := msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
|
||||||
assert.EqualValues(resp.Index, 1000, "Wrong Index")
|
|
||||||
assert.Len(resp.Deployments, 1, "Deployments")
|
|
||||||
assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup the deployments with a valid token
|
if tc.expectedError != "" {
|
||||||
{
|
must.ErrorContains(t, err, tc.expectedError)
|
||||||
get.AuthToken = validToken.SecretID
|
} else {
|
||||||
var resp structs.DeploymentListResponse
|
must.NoError(t, err)
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
|
require.Equal(t, uint64(1001), resp.Index, "Bad index: %d %d", resp.Index, 1001)
|
||||||
assert.EqualValues(resp.Index, 1000, "Wrong Index")
|
|
||||||
assert.Len(resp.Deployments, 1, "Deployments")
|
got := make([]string, len(resp.Deployments))
|
||||||
assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
|
for i, eval := range resp.Deployments {
|
||||||
|
got[i] = eval.ID
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t, got, tc.expectedDeployments)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1201,18 +1270,17 @@ func TestDeploymentEndpoint_List_Blocking(t *testing.T) {
|
||||||
state := s1.fsm.State()
|
state := s1.fsm.State()
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
testutil.WaitForLeader(t, s1.RPC)
|
testutil.WaitForLeader(t, s1.RPC)
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// Create the deployment
|
// Create the deployment
|
||||||
j := mock.Job()
|
j := mock.Job()
|
||||||
d := mock.Deployment()
|
d := mock.Deployment()
|
||||||
d.JobID = j.ID
|
d.JobID = j.ID
|
||||||
|
|
||||||
assert.Nil(state.UpsertJob(structs.MsgTypeTestSetup, 999, nil, j), "UpsertJob")
|
must.Nil(t, state.UpsertJob(structs.MsgTypeTestSetup, 999, nil, j), must.Sprint("UpsertJob"))
|
||||||
|
|
||||||
// Upsert alloc triggers watches
|
// Upsert alloc triggers watches
|
||||||
time.AfterFunc(100*time.Millisecond, func() {
|
time.AfterFunc(100*time.Millisecond, func() {
|
||||||
assert.Nil(state.UpsertDeployment(3, d), "UpsertDeployment")
|
must.Nil(t, state.UpsertDeployment(3, d), must.Sprint("UpsertDeployment"))
|
||||||
})
|
})
|
||||||
|
|
||||||
req := &structs.DeploymentListRequest{
|
req := &structs.DeploymentListRequest{
|
||||||
|
@ -1224,31 +1292,28 @@ func TestDeploymentEndpoint_List_Blocking(t *testing.T) {
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp), "RPC")
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp), must.Sprint("RPC"))
|
||||||
assert.EqualValues(resp.Index, 3, "Wrong Index")
|
must.Eq(t, resp.Index, 3, must.Sprint("Wrong Index"))
|
||||||
assert.Len(resp.Deployments, 1, "Deployments")
|
must.Len(t, 1, resp.Deployments, must.Sprint("Deployments"))
|
||||||
assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
|
must.Eq(t, resp.Deployments[0].ID, d.ID, must.Sprint("Deployment ID"))
|
||||||
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
elapsed := time.Since(start)
|
||||||
t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
|
must.Greater(t, 100*time.Millisecond, elapsed, must.Sprintf("should block (returned in %s) %#v", elapsed, resp))
|
||||||
}
|
|
||||||
|
|
||||||
// Deployment updates trigger watches
|
// Deployment updates trigger watches
|
||||||
d2 := d.Copy()
|
d2 := d.Copy()
|
||||||
d2.Status = structs.DeploymentStatusPaused
|
d2.Status = structs.DeploymentStatusPaused
|
||||||
time.AfterFunc(100*time.Millisecond, func() {
|
time.AfterFunc(100*time.Millisecond, func() {
|
||||||
assert.Nil(state.UpsertDeployment(5, d2), "UpsertDeployment")
|
must.Nil(t, state.UpsertDeployment(5, d2), must.Sprint("UpsertDeployment"))
|
||||||
})
|
})
|
||||||
|
|
||||||
req.MinQueryIndex = 3
|
req.MinQueryIndex = 3
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
var resp2 structs.DeploymentListResponse
|
var resp2 structs.DeploymentListResponse
|
||||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp2), "RPC")
|
must.Nil(t, msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp2), must.Sprint("RPC"))
|
||||||
assert.EqualValues(5, resp2.Index, "Wrong Index")
|
must.Eq(t, 5, resp2.Index, must.Sprint("Wrong Index"))
|
||||||
assert.Len(resp2.Deployments, 1, "Deployments")
|
must.Len(t, 1, resp2.Deployments, must.Sprint("Deployments"))
|
||||||
assert.Equal(d2.ID, resp2.Deployments[0].ID, "Deployment ID")
|
must.StrContains(t, d2.ID, resp2.Deployments[0].ID, must.Sprint("Deployment ID"))
|
||||||
if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
|
must.Greater(t, 100*time.Millisecond, elapsed, must.Sprintf("should block (returned in %s) %#v", elapsed, resp2))
|
||||||
t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
|
@ -1258,6 +1323,12 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
testutil.WaitForLeader(t, s1.RPC)
|
testutil.WaitForLeader(t, s1.RPC)
|
||||||
|
|
||||||
|
// Create dev namespace
|
||||||
|
devNS := mock.Namespace()
|
||||||
|
devNS.Name = "non-default"
|
||||||
|
err := s1.fsm.State().UpsertNamespaces(999, []*structs.Namespace{devNS})
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
// create a set of deployments. these are in the order that the
|
// create a set of deployments. these are in the order that the
|
||||||
// state store will return them from the iterator (sorted by key),
|
// state store will return them from the iterator (sorted by key),
|
||||||
// for ease of writing tests
|
// for ease of writing tests
|
||||||
|
@ -1269,7 +1340,7 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{id: "aaaa1111-3350-4b4b-d185-0e1992ed43e9"}, // 0
|
{id: "aaaa1111-3350-4b4b-d185-0e1992ed43e9"}, // 0
|
||||||
{id: "aaaaaa22-3350-4b4b-d185-0e1992ed43e9"}, // 1
|
{id: "aaaaaa22-3350-4b4b-d185-0e1992ed43e9"}, // 1
|
||||||
{id: "aaaaaa33-3350-4b4b-d185-0e1992ed43e9", namespace: "non-default"}, // 2
|
{id: "aaaaaa33-3350-4b4b-d185-0e1992ed43e9", namespace: devNS.Name}, // 2
|
||||||
{id: "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"}, // 3
|
{id: "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"}, // 3
|
||||||
{id: "aaaaaabb-3350-4b4b-d185-0e1992ed43e9"}, // 4
|
{id: "aaaaaabb-3350-4b4b-d185-0e1992ed43e9"}, // 4
|
||||||
{id: "aaaaaacc-3350-4b4b-d185-0e1992ed43e9"}, // 5
|
{id: "aaaaaacc-3350-4b4b-d185-0e1992ed43e9"}, // 5
|
||||||
|
@ -1294,7 +1365,7 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
if m.namespace != "" { // defaults to "default"
|
if m.namespace != "" { // defaults to "default"
|
||||||
deployment.Namespace = m.namespace
|
deployment.Namespace = m.namespace
|
||||||
}
|
}
|
||||||
require.NoError(t, state.UpsertDeployment(index, deployment))
|
must.NoError(t, state.UpsertDeployment(index, deployment))
|
||||||
}
|
}
|
||||||
|
|
||||||
aclToken := mock.CreatePolicyAndToken(t, state, 1100, "test-valid-read",
|
aclToken := mock.CreatePolicyAndToken(t, state, 1100, "test-valid-read",
|
||||||
|
@ -1432,6 +1503,18 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
"bbbb1111-3350-4b4b-d185-0e1992ed43e9",
|
"bbbb1111-3350-4b4b-d185-0e1992ed43e9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test15 size-2 page-2 all namespaces with prefix",
|
||||||
|
namespace: "*",
|
||||||
|
prefix: "aaaa",
|
||||||
|
pageSize: 2,
|
||||||
|
nextToken: "aaaaaa33-3350-4b4b-d185-0e1992ed43e9",
|
||||||
|
expectedNextToken: "aaaaaabb-3350-4b4b-d185-0e1992ed43e9",
|
||||||
|
expectedIDs: []string{
|
||||||
|
"aaaaaa33-3350-4b4b-d185-0e1992ed43e9",
|
||||||
|
"aaaaaaaa-3350-4b4b-d185-0e1992ed43e9",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
@ -1450,10 +1533,10 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
var resp structs.DeploymentListResponse
|
var resp structs.DeploymentListResponse
|
||||||
err := msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp)
|
err := msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp)
|
||||||
if tc.expectedError == "" {
|
if tc.expectedError == "" {
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
require.Error(t, err)
|
must.Error(t, err)
|
||||||
require.Contains(t, err.Error(), tc.expectedError)
|
must.ErrorContains(t, err, tc.expectedError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1461,8 +1544,8 @@ func TestDeploymentEndpoint_List_Pagination(t *testing.T) {
|
||||||
for _, deployment := range resp.Deployments {
|
for _, deployment := range resp.Deployments {
|
||||||
gotIDs = append(gotIDs, deployment.ID)
|
gotIDs = append(gotIDs, deployment.ID)
|
||||||
}
|
}
|
||||||
require.Equal(t, tc.expectedIDs, gotIDs, "unexpected page of deployments")
|
must.Eq(t, tc.expectedIDs, gotIDs, must.Sprint("unexpected page of deployments"))
|
||||||
require.Equal(t, tc.expectedNextToken, resp.QueryMeta.NextToken, "unexpected NextToken")
|
must.Eq(t, tc.expectedNextToken, resp.QueryMeta.NextToken, must.Sprint("unexpected NextToken"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -681,7 +681,8 @@ func deploymentNamespaceFilter(namespace string) func(interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.Namespace != namespace
|
return namespace != structs.AllNamespacesSentinel &&
|
||||||
|
d.Namespace != namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9858,6 +9858,14 @@ func (d *Deployment) GoString() string {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNamespace implements the NamespaceGetter interface, required for pagination.
|
||||||
|
func (d *Deployment) GetNamespace() string {
|
||||||
|
if d == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return d.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
// DeploymentState tracks the state of a deployment for a given task group.
|
// DeploymentState tracks the state of a deployment for a given task group.
|
||||||
type DeploymentState struct {
|
type DeploymentState struct {
|
||||||
// AutoRevert marks whether the task group has indicated the job should be
|
// AutoRevert marks whether the task group has indicated the job should be
|
||||||
|
|
Loading…
Reference in New Issue