github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/deployment_endpoint_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	memdb "github.com/hashicorp/go-memdb"
     8  	msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
     9  	"github.com/hashicorp/nomad/acl"
    10  	"github.com/hashicorp/nomad/helper"
    11  	"github.com/hashicorp/nomad/nomad/mock"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/hashicorp/nomad/testutil"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestDeploymentEndpoint_GetDeployment(t *testing.T) {
    18  	t.Parallel()
    19  	s1 := TestServer(t, nil)
    20  	defer s1.Shutdown()
    21  	codec := rpcClient(t, s1)
    22  	testutil.WaitForLeader(t, s1.RPC)
    23  	assert := assert.New(t)
    24  
    25  	// Create the deployment
    26  	j := mock.Job()
    27  	d := mock.Deployment()
    28  	d.JobID = j.ID
    29  	state := s1.fsm.State()
    30  
    31  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
    32  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
    33  
    34  	// Lookup the deployments
    35  	get := &structs.DeploymentSpecificRequest{
    36  		DeploymentID: d.ID,
    37  		QueryOptions: structs.QueryOptions{
    38  			Region:    "global",
    39  			Namespace: structs.DefaultNamespace,
    40  		},
    41  	}
    42  	var resp structs.SingleDeploymentResponse
    43  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp), "RPC")
    44  	assert.EqualValues(resp.Index, 1000, "resp.Index")
    45  	assert.Equal(d, resp.Deployment, "Returned deployment not equal")
    46  }
    47  
    48  func TestDeploymentEndpoint_GetDeployment_ACL(t *testing.T) {
    49  	t.Parallel()
    50  	s1, root := TestACLServer(t, nil)
    51  	defer s1.Shutdown()
    52  	codec := rpcClient(t, s1)
    53  	testutil.WaitForLeader(t, s1.RPC)
    54  	assert := assert.New(t)
    55  
    56  	// Create the deployment
    57  	j := mock.Job()
    58  	d := mock.Deployment()
    59  	d.JobID = j.ID
    60  	state := s1.fsm.State()
    61  
    62  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
    63  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
    64  
    65  	// Create the namespace policy and tokens
    66  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
    67  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
    68  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
    69  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
    70  
    71  	// Lookup the deployments without a token and expect failure
    72  	get := &structs.DeploymentSpecificRequest{
    73  		DeploymentID: d.ID,
    74  		QueryOptions: structs.QueryOptions{
    75  			Region:    "global",
    76  			Namespace: structs.DefaultNamespace,
    77  		},
    78  	}
    79  	var resp structs.SingleDeploymentResponse
    80  	assert.NotNil(msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp), "RPC")
    81  
    82  	// Try with a good token
    83  	get.AuthToken = validToken.SecretID
    84  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp), "RPC")
    85  	assert.EqualValues(resp.Index, 1000, "resp.Index")
    86  	assert.Equal(d, resp.Deployment, "Returned deployment not equal")
    87  
    88  	// Try with a bad token
    89  	get.AuthToken = invalidToken.SecretID
    90  	err := msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp)
    91  	assert.NotNil(err, "RPC")
    92  	assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
    93  
    94  	// Try with a root token
    95  	get.AuthToken = root.SecretID
    96  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp), "RPC")
    97  	assert.EqualValues(resp.Index, 1000, "resp.Index")
    98  	assert.Equal(d, resp.Deployment, "Returned deployment not equal")
    99  }
   100  
   101  func TestDeploymentEndpoint_GetDeployment_Blocking(t *testing.T) {
   102  	t.Parallel()
   103  	s1 := TestServer(t, nil)
   104  	defer s1.Shutdown()
   105  	codec := rpcClient(t, s1)
   106  	testutil.WaitForLeader(t, s1.RPC)
   107  	state := s1.fsm.State()
   108  	assert := assert.New(t)
   109  
   110  	// Create the deployments
   111  	j1 := mock.Job()
   112  	j2 := mock.Job()
   113  	d1 := mock.Deployment()
   114  	d1.JobID = j1.ID
   115  	d2 := mock.Deployment()
   116  	d2.JobID = j2.ID
   117  
   118  	assert.Nil(state.UpsertJob(98, j1), "UpsertJob")
   119  	assert.Nil(state.UpsertJob(99, j2), "UpsertJob")
   120  
   121  	// Upsert a deployment we are not interested in first.
   122  	time.AfterFunc(100*time.Millisecond, func() {
   123  		assert.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment")
   124  	})
   125  
   126  	// Upsert another deployment later which should trigger the watch.
   127  	time.AfterFunc(200*time.Millisecond, func() {
   128  		assert.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment")
   129  	})
   130  
   131  	// Lookup the deployments
   132  	get := &structs.DeploymentSpecificRequest{
   133  		DeploymentID: d2.ID,
   134  		QueryOptions: structs.QueryOptions{
   135  			Region:        "global",
   136  			Namespace:     structs.DefaultNamespace,
   137  			MinQueryIndex: 150,
   138  		},
   139  	}
   140  	start := time.Now()
   141  	var resp structs.SingleDeploymentResponse
   142  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.GetDeployment", get, &resp), "RPC")
   143  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
   144  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
   145  	}
   146  	assert.EqualValues(resp.Index, 200, "resp.Index")
   147  	assert.Equal(d2, resp.Deployment, "deployments equal")
   148  }
   149  
   150  func TestDeploymentEndpoint_Fail(t *testing.T) {
   151  	t.Parallel()
   152  	s1 := TestServer(t, func(c *Config) {
   153  		c.NumSchedulers = 0 // Prevent automatic dequeue
   154  	})
   155  	defer s1.Shutdown()
   156  	codec := rpcClient(t, s1)
   157  	testutil.WaitForLeader(t, s1.RPC)
   158  	assert := assert.New(t)
   159  
   160  	// Create the deployment
   161  	j := mock.Job()
   162  	d := mock.Deployment()
   163  	d.JobID = j.ID
   164  	state := s1.fsm.State()
   165  
   166  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   167  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   168  
   169  	// Mark the deployment as failed
   170  	req := &structs.DeploymentFailRequest{
   171  		DeploymentID: d.ID,
   172  		WriteRequest: structs.WriteRequest{Region: "global"},
   173  	}
   174  
   175  	// Fetch the response
   176  	var resp structs.DeploymentUpdateResponse
   177  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Fail", req, &resp), "RPC")
   178  	assert.NotEqual(resp.Index, uint64(0), "bad response index")
   179  
   180  	// Lookup the evaluation
   181  	ws := memdb.NewWatchSet()
   182  	eval, err := state.EvalByID(ws, resp.EvalID)
   183  	assert.Nil(err, "EvalByID failed")
   184  	assert.NotNil(eval, "Expect eval")
   185  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   186  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   187  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   188  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   189  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   190  
   191  	// Lookup the deployment
   192  	dout, err := state.DeploymentByID(ws, d.ID)
   193  	assert.Nil(err, "DeploymentByID failed")
   194  	assert.Equal(dout.Status, structs.DeploymentStatusFailed, "wrong status")
   195  	assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionFailedByUser, "wrong status description")
   196  	assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   197  }
   198  
   199  func TestDeploymentEndpoint_Fail_ACL(t *testing.T) {
   200  	t.Parallel()
   201  	s1, _ := TestACLServer(t, func(c *Config) {
   202  		c.NumSchedulers = 0 // Prevent automatic dequeue
   203  	})
   204  	defer s1.Shutdown()
   205  	codec := rpcClient(t, s1)
   206  	testutil.WaitForLeader(t, s1.RPC)
   207  	assert := assert.New(t)
   208  
   209  	// Create the deployment
   210  	j := mock.Job()
   211  	d := mock.Deployment()
   212  	d.JobID = j.ID
   213  	state := s1.fsm.State()
   214  
   215  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   216  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   217  
   218  	// Create the namespace policy and tokens
   219  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
   220  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
   221  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
   222  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
   223  
   224  	// Mark the deployment as failed
   225  	req := &structs.DeploymentFailRequest{
   226  		DeploymentID: d.ID,
   227  		WriteRequest: structs.WriteRequest{Region: "global"},
   228  	}
   229  
   230  	// Try with no token and expect permission denied
   231  	{
   232  		var resp structs.DeploymentUpdateResponse
   233  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Fail", req, &resp)
   234  		assert.NotNil(err)
   235  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   236  	}
   237  
   238  	// Try with an invalid token
   239  	{
   240  		req.AuthToken = invalidToken.SecretID
   241  		var resp structs.DeploymentUpdateResponse
   242  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Fail", req, &resp)
   243  		assert.NotNil(err)
   244  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   245  	}
   246  
   247  	// Try with a valid token
   248  	{
   249  		req.AuthToken = validToken.SecretID
   250  		var resp structs.DeploymentUpdateResponse
   251  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Fail", req, &resp), "RPC")
   252  		assert.NotEqual(resp.Index, uint64(0), "bad response index")
   253  
   254  		// Lookup the evaluation
   255  		ws := memdb.NewWatchSet()
   256  		eval, err := state.EvalByID(ws, resp.EvalID)
   257  		assert.Nil(err, "EvalByID failed")
   258  		assert.NotNil(eval, "Expect eval")
   259  		assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   260  		assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   261  		assert.Equal(eval.JobID, d.JobID, "eval job id")
   262  		assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   263  		assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   264  
   265  		// Lookup the deployment
   266  		dout, err := state.DeploymentByID(ws, d.ID)
   267  		assert.Nil(err, "DeploymentByID failed")
   268  		assert.Equal(dout.Status, structs.DeploymentStatusFailed, "wrong status")
   269  		assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionFailedByUser, "wrong status description")
   270  		assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   271  	}
   272  }
   273  
   274  func TestDeploymentEndpoint_Fail_Rollback(t *testing.T) {
   275  	t.Parallel()
   276  	s1 := TestServer(t, func(c *Config) {
   277  		c.NumSchedulers = 0 // Prevent automatic dequeue
   278  	})
   279  	defer s1.Shutdown()
   280  	codec := rpcClient(t, s1)
   281  	testutil.WaitForLeader(t, s1.RPC)
   282  	assert := assert.New(t)
   283  	state := s1.fsm.State()
   284  
   285  	// Create the original job
   286  	j := mock.Job()
   287  	j.Stable = true
   288  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   289  	j.TaskGroups[0].Update.MaxParallel = 2
   290  	j.TaskGroups[0].Update.AutoRevert = true
   291  	assert.Nil(state.UpsertJob(998, j), "UpsertJob")
   292  
   293  	// Create the second job, deployment and alloc
   294  	j2 := j.Copy()
   295  	j2.Stable = false
   296  	// Modify the job to make its specification different
   297  	j2.Meta["foo"] = "bar"
   298  
   299  	d := mock.Deployment()
   300  	d.TaskGroups["web"].AutoRevert = true
   301  	d.JobID = j2.ID
   302  	d.JobVersion = j2.Version
   303  
   304  	a := mock.Alloc()
   305  	a.JobID = j.ID
   306  	a.DeploymentID = d.ID
   307  
   308  	assert.Nil(state.UpsertJob(999, j2), "UpsertJob")
   309  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   310  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   311  
   312  	// Mark the deployment as failed
   313  	req := &structs.DeploymentFailRequest{
   314  		DeploymentID: d.ID,
   315  		WriteRequest: structs.WriteRequest{Region: "global"},
   316  	}
   317  
   318  	// Fetch the response
   319  	var resp structs.DeploymentUpdateResponse
   320  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Fail", req, &resp), "RPC")
   321  	assert.NotEqual(resp.Index, uint64(0), "bad response index")
   322  	assert.NotNil(resp.RevertedJobVersion, "bad revert version")
   323  	assert.EqualValues(0, *resp.RevertedJobVersion, "bad revert version")
   324  
   325  	// Lookup the evaluation
   326  	ws := memdb.NewWatchSet()
   327  	eval, err := state.EvalByID(ws, resp.EvalID)
   328  	assert.Nil(err, "EvalByID failed")
   329  	assert.NotNil(eval, "Expect eval")
   330  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   331  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   332  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   333  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   334  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   335  
   336  	// Lookup the deployment
   337  	expectedDesc := structs.DeploymentStatusDescriptionRollback(structs.DeploymentStatusDescriptionFailedByUser, 0)
   338  	dout, err := state.DeploymentByID(ws, d.ID)
   339  	assert.Nil(err, "DeploymentByID failed")
   340  	assert.Equal(dout.Status, structs.DeploymentStatusFailed, "wrong status")
   341  	assert.Equal(dout.StatusDescription, expectedDesc, "wrong status description")
   342  	assert.Equal(resp.DeploymentModifyIndex, dout.ModifyIndex, "wrong modify index")
   343  
   344  	// Lookup the job
   345  	jout, err := state.JobByID(ws, j.Namespace, j.ID)
   346  	assert.Nil(err, "JobByID")
   347  	assert.NotNil(jout, "job")
   348  	assert.EqualValues(2, jout.Version, "reverted job version")
   349  }
   350  
   351  func TestDeploymentEndpoint_Pause(t *testing.T) {
   352  	t.Parallel()
   353  	s1 := TestServer(t, func(c *Config) {
   354  		c.NumSchedulers = 0 // Prevent automatic dequeue
   355  	})
   356  	defer s1.Shutdown()
   357  	codec := rpcClient(t, s1)
   358  	testutil.WaitForLeader(t, s1.RPC)
   359  	assert := assert.New(t)
   360  
   361  	// Create the deployment
   362  	j := mock.Job()
   363  	d := mock.Deployment()
   364  	d.JobID = j.ID
   365  	state := s1.fsm.State()
   366  
   367  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   368  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   369  
   370  	// Mark the deployment as failed
   371  	req := &structs.DeploymentPauseRequest{
   372  		DeploymentID: d.ID,
   373  		Pause:        true,
   374  		WriteRequest: structs.WriteRequest{Region: "global"},
   375  	}
   376  
   377  	// Fetch the response
   378  	var resp structs.DeploymentUpdateResponse
   379  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Pause", req, &resp), "RPC")
   380  	assert.NotEqual(resp.Index, uint64(0), "bad response index")
   381  	assert.Zero(resp.EvalCreateIndex, "Shouldn't create eval")
   382  	assert.Zero(resp.EvalID, "Shouldn't create eval")
   383  
   384  	// Lookup the deployment
   385  	ws := memdb.NewWatchSet()
   386  	dout, err := state.DeploymentByID(ws, d.ID)
   387  	assert.Nil(err, "DeploymentByID failed")
   388  	assert.Equal(dout.Status, structs.DeploymentStatusPaused, "wrong status")
   389  	assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionPaused, "wrong status description")
   390  	assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   391  }
   392  
   393  func TestDeploymentEndpoint_Pause_ACL(t *testing.T) {
   394  	t.Parallel()
   395  	s1, _ := TestACLServer(t, func(c *Config) {
   396  		c.NumSchedulers = 0 // Prevent automatic dequeue
   397  	})
   398  	defer s1.Shutdown()
   399  	codec := rpcClient(t, s1)
   400  	testutil.WaitForLeader(t, s1.RPC)
   401  	assert := assert.New(t)
   402  
   403  	// Create the deployment
   404  	j := mock.Job()
   405  	d := mock.Deployment()
   406  	d.JobID = j.ID
   407  	state := s1.fsm.State()
   408  
   409  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   410  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   411  
   412  	// Create the namespace policy and tokens
   413  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
   414  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
   415  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
   416  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
   417  
   418  	// Mark the deployment as failed
   419  	req := &structs.DeploymentPauseRequest{
   420  		DeploymentID: d.ID,
   421  		Pause:        true,
   422  		WriteRequest: structs.WriteRequest{Region: "global"},
   423  	}
   424  
   425  	// Try with no token and expect permission denied
   426  	{
   427  		var resp structs.DeploymentUpdateResponse
   428  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Pause", req, &resp)
   429  		assert.NotNil(err)
   430  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   431  	}
   432  
   433  	// Try with an invalid token
   434  	{
   435  		req.AuthToken = invalidToken.SecretID
   436  		var resp structs.DeploymentUpdateResponse
   437  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Pause", req, &resp)
   438  		assert.NotNil(err)
   439  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   440  	}
   441  
   442  	// Fetch the response with a valid token
   443  	{
   444  		req.AuthToken = validToken.SecretID
   445  		var resp structs.DeploymentUpdateResponse
   446  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Pause", req, &resp), "RPC")
   447  		assert.NotEqual(resp.Index, uint64(0), "bad response index")
   448  		assert.Zero(resp.EvalCreateIndex, "Shouldn't create eval")
   449  		assert.Zero(resp.EvalID, "Shouldn't create eval")
   450  
   451  		// Lookup the deployment
   452  		ws := memdb.NewWatchSet()
   453  		dout, err := state.DeploymentByID(ws, d.ID)
   454  		assert.Nil(err, "DeploymentByID failed")
   455  		assert.Equal(dout.Status, structs.DeploymentStatusPaused, "wrong status")
   456  		assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionPaused, "wrong status description")
   457  		assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   458  	}
   459  }
   460  
   461  func TestDeploymentEndpoint_Promote(t *testing.T) {
   462  	t.Parallel()
   463  	s1 := TestServer(t, func(c *Config) {
   464  		c.NumSchedulers = 0 // Prevent automatic dequeue
   465  	})
   466  	defer s1.Shutdown()
   467  	codec := rpcClient(t, s1)
   468  	testutil.WaitForLeader(t, s1.RPC)
   469  	assert := assert.New(t)
   470  
   471  	// Create the deployment, job and canary
   472  	j := mock.Job()
   473  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   474  	j.TaskGroups[0].Update.MaxParallel = 2
   475  	j.TaskGroups[0].Update.Canary = 1
   476  	d := mock.Deployment()
   477  	d.TaskGroups["web"].DesiredCanaries = 1
   478  	d.JobID = j.ID
   479  	a := mock.Alloc()
   480  	d.TaskGroups[a.TaskGroup].PlacedCanaries = []string{a.ID}
   481  	a.DeploymentID = d.ID
   482  	a.DeploymentStatus = &structs.AllocDeploymentStatus{
   483  		Healthy: helper.BoolToPtr(true),
   484  	}
   485  
   486  	state := s1.fsm.State()
   487  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   488  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   489  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   490  
   491  	// Promote the deployment
   492  	req := &structs.DeploymentPromoteRequest{
   493  		DeploymentID: d.ID,
   494  		All:          true,
   495  		WriteRequest: structs.WriteRequest{Region: "global"},
   496  	}
   497  
   498  	// Fetch the response
   499  	var resp structs.DeploymentUpdateResponse
   500  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Promote", req, &resp), "RPC")
   501  	assert.NotEqual(resp.Index, uint64(0), "bad response index")
   502  
   503  	// Lookup the evaluation
   504  	ws := memdb.NewWatchSet()
   505  	eval, err := state.EvalByID(ws, resp.EvalID)
   506  	assert.Nil(err, "EvalByID failed")
   507  	assert.NotNil(eval, "Expect eval")
   508  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   509  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   510  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   511  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   512  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   513  
   514  	// Lookup the deployment
   515  	dout, err := state.DeploymentByID(ws, d.ID)
   516  	assert.Nil(err, "DeploymentByID failed")
   517  	assert.Equal(dout.Status, structs.DeploymentStatusRunning, "wrong status")
   518  	assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionRunning, "wrong status description")
   519  	assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   520  	assert.Len(dout.TaskGroups, 1, "should have one group")
   521  	assert.Contains(dout.TaskGroups, "web", "should have web group")
   522  	assert.True(dout.TaskGroups["web"].Promoted, "web group should be promoted")
   523  }
   524  
   525  func TestDeploymentEndpoint_Promote_ACL(t *testing.T) {
   526  	t.Parallel()
   527  	s1, _ := TestACLServer(t, func(c *Config) {
   528  		c.NumSchedulers = 0 // Prevent automatic dequeue
   529  	})
   530  	defer s1.Shutdown()
   531  	codec := rpcClient(t, s1)
   532  	testutil.WaitForLeader(t, s1.RPC)
   533  	assert := assert.New(t)
   534  
   535  	// Create the deployment, job and canary
   536  	j := mock.Job()
   537  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   538  	j.TaskGroups[0].Update.MaxParallel = 2
   539  	j.TaskGroups[0].Update.Canary = 1
   540  	d := mock.Deployment()
   541  	d.TaskGroups["web"].DesiredCanaries = 1
   542  	d.JobID = j.ID
   543  	a := mock.Alloc()
   544  	d.TaskGroups[a.TaskGroup].PlacedCanaries = []string{a.ID}
   545  	a.DeploymentID = d.ID
   546  	a.DeploymentStatus = &structs.AllocDeploymentStatus{
   547  		Healthy: helper.BoolToPtr(true),
   548  	}
   549  
   550  	state := s1.fsm.State()
   551  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   552  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   553  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   554  
   555  	// Create the namespace policy and tokens
   556  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
   557  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
   558  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
   559  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
   560  
   561  	// Promote the deployment
   562  	req := &structs.DeploymentPromoteRequest{
   563  		DeploymentID: d.ID,
   564  		All:          true,
   565  		WriteRequest: structs.WriteRequest{Region: "global"},
   566  	}
   567  
   568  	// Try with no token and expect permission denied
   569  	{
   570  		var resp structs.DeploymentUpdateResponse
   571  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Promote", req, &resp)
   572  		assert.NotNil(err)
   573  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   574  	}
   575  
   576  	// Try with an invalid token
   577  	{
   578  		req.AuthToken = invalidToken.SecretID
   579  		var resp structs.DeploymentUpdateResponse
   580  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Promote", req, &resp)
   581  		assert.NotNil(err)
   582  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   583  	}
   584  
   585  	// Fetch the response with a valid token
   586  	{
   587  		req.AuthToken = validToken.SecretID
   588  		var resp structs.DeploymentUpdateResponse
   589  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Promote", req, &resp), "RPC")
   590  		assert.NotEqual(resp.Index, uint64(0), "bad response index")
   591  
   592  		// Lookup the evaluation
   593  		ws := memdb.NewWatchSet()
   594  		eval, err := state.EvalByID(ws, resp.EvalID)
   595  		assert.Nil(err, "EvalByID failed")
   596  		assert.NotNil(eval, "Expect eval")
   597  		assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   598  		assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   599  		assert.Equal(eval.JobID, d.JobID, "eval job id")
   600  		assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   601  		assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   602  
   603  		// Lookup the deployment
   604  		dout, err := state.DeploymentByID(ws, d.ID)
   605  		assert.Nil(err, "DeploymentByID failed")
   606  		assert.Equal(dout.Status, structs.DeploymentStatusRunning, "wrong status")
   607  		assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionRunning, "wrong status description")
   608  		assert.Equal(dout.ModifyIndex, resp.DeploymentModifyIndex, "wrong modify index")
   609  		assert.Len(dout.TaskGroups, 1, "should have one group")
   610  		assert.Contains(dout.TaskGroups, "web", "should have web group")
   611  		assert.True(dout.TaskGroups["web"].Promoted, "web group should be promoted")
   612  	}
   613  }
   614  
   615  func TestDeploymentEndpoint_SetAllocHealth(t *testing.T) {
   616  	t.Parallel()
   617  	s1 := TestServer(t, func(c *Config) {
   618  		c.NumSchedulers = 0 // Prevent automatic dequeue
   619  	})
   620  	defer s1.Shutdown()
   621  	codec := rpcClient(t, s1)
   622  	testutil.WaitForLeader(t, s1.RPC)
   623  	assert := assert.New(t)
   624  
   625  	// Create the deployment, job and canary
   626  	j := mock.Job()
   627  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   628  	j.TaskGroups[0].Update.MaxParallel = 2
   629  	d := mock.Deployment()
   630  	d.JobID = j.ID
   631  	a := mock.Alloc()
   632  	a.JobID = j.ID
   633  	a.DeploymentID = d.ID
   634  
   635  	state := s1.fsm.State()
   636  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   637  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   638  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   639  
   640  	// Set the alloc as healthy
   641  	req := &structs.DeploymentAllocHealthRequest{
   642  		DeploymentID:         d.ID,
   643  		HealthyAllocationIDs: []string{a.ID},
   644  		WriteRequest:         structs.WriteRequest{Region: "global"},
   645  	}
   646  
   647  	// Fetch the response
   648  	var resp structs.DeploymentUpdateResponse
   649  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp), "RPC")
   650  	assert.NotZero(resp.Index, "bad response index")
   651  
   652  	// Lookup the evaluation
   653  	ws := memdb.NewWatchSet()
   654  	eval, err := state.EvalByID(ws, resp.EvalID)
   655  	assert.Nil(err, "EvalByID failed")
   656  	assert.NotNil(eval, "Expect eval")
   657  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   658  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   659  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   660  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   661  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   662  
   663  	// Lookup the deployment
   664  	dout, err := state.DeploymentByID(ws, d.ID)
   665  	assert.Nil(err, "DeploymentByID failed")
   666  	assert.Equal(dout.Status, structs.DeploymentStatusRunning, "wrong status")
   667  	assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionRunning, "wrong status description")
   668  	assert.Equal(resp.DeploymentModifyIndex, dout.ModifyIndex, "wrong modify index")
   669  	assert.Len(dout.TaskGroups, 1, "should have one group")
   670  	assert.Contains(dout.TaskGroups, "web", "should have web group")
   671  	assert.Equal(1, dout.TaskGroups["web"].HealthyAllocs, "should have one healthy")
   672  
   673  	// Lookup the allocation
   674  	aout, err := state.AllocByID(ws, a.ID)
   675  	assert.Nil(err, "AllocByID")
   676  	assert.NotNil(aout, "alloc")
   677  	assert.NotNil(aout.DeploymentStatus, "alloc deployment status")
   678  	assert.NotNil(aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   679  	assert.True(*aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   680  }
   681  
   682  func TestDeploymentEndpoint_SetAllocHealth_ACL(t *testing.T) {
   683  	t.Parallel()
   684  	s1, _ := TestACLServer(t, func(c *Config) {
   685  		c.NumSchedulers = 0 // Prevent automatic dequeue
   686  	})
   687  	defer s1.Shutdown()
   688  	codec := rpcClient(t, s1)
   689  	testutil.WaitForLeader(t, s1.RPC)
   690  	assert := assert.New(t)
   691  
   692  	// Create the deployment, job and canary
   693  	j := mock.Job()
   694  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   695  	j.TaskGroups[0].Update.MaxParallel = 2
   696  	d := mock.Deployment()
   697  	d.JobID = j.ID
   698  	a := mock.Alloc()
   699  	a.JobID = j.ID
   700  	a.DeploymentID = d.ID
   701  
   702  	state := s1.fsm.State()
   703  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   704  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   705  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   706  
   707  	// Create the namespace policy and tokens
   708  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
   709  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
   710  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
   711  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
   712  
   713  	// Set the alloc as healthy
   714  	req := &structs.DeploymentAllocHealthRequest{
   715  		DeploymentID:         d.ID,
   716  		HealthyAllocationIDs: []string{a.ID},
   717  		WriteRequest:         structs.WriteRequest{Region: "global"},
   718  	}
   719  
   720  	// Try with no token and expect permission denied
   721  	{
   722  		var resp structs.DeploymentUpdateResponse
   723  		err := msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp)
   724  		assert.NotNil(err)
   725  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   726  	}
   727  
   728  	// Try with an invalid token
   729  	{
   730  		req.AuthToken = invalidToken.SecretID
   731  		var resp structs.DeploymentUpdateResponse
   732  		err := msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp)
   733  		assert.NotNil(err)
   734  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
   735  	}
   736  
   737  	// Fetch the response with a valid token
   738  	{
   739  		req.AuthToken = validToken.SecretID
   740  		var resp structs.DeploymentUpdateResponse
   741  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp), "RPC")
   742  		assert.NotZero(resp.Index, "bad response index")
   743  
   744  		// Lookup the evaluation
   745  		ws := memdb.NewWatchSet()
   746  		eval, err := state.EvalByID(ws, resp.EvalID)
   747  		assert.Nil(err, "EvalByID failed")
   748  		assert.NotNil(eval, "Expect eval")
   749  		assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   750  		assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   751  		assert.Equal(eval.JobID, d.JobID, "eval job id")
   752  		assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   753  		assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   754  
   755  		// Lookup the deployment
   756  		dout, err := state.DeploymentByID(ws, d.ID)
   757  		assert.Nil(err, "DeploymentByID failed")
   758  		assert.Equal(dout.Status, structs.DeploymentStatusRunning, "wrong status")
   759  		assert.Equal(dout.StatusDescription, structs.DeploymentStatusDescriptionRunning, "wrong status description")
   760  		assert.Equal(resp.DeploymentModifyIndex, dout.ModifyIndex, "wrong modify index")
   761  		assert.Len(dout.TaskGroups, 1, "should have one group")
   762  		assert.Contains(dout.TaskGroups, "web", "should have web group")
   763  		assert.Equal(1, dout.TaskGroups["web"].HealthyAllocs, "should have one healthy")
   764  
   765  		// Lookup the allocation
   766  		aout, err := state.AllocByID(ws, a.ID)
   767  		assert.Nil(err, "AllocByID")
   768  		assert.NotNil(aout, "alloc")
   769  		assert.NotNil(aout.DeploymentStatus, "alloc deployment status")
   770  		assert.NotNil(aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   771  		assert.True(*aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   772  	}
   773  }
   774  
   775  func TestDeploymentEndpoint_SetAllocHealth_Rollback(t *testing.T) {
   776  	t.Parallel()
   777  	s1 := TestServer(t, func(c *Config) {
   778  		c.NumSchedulers = 0 // Prevent automatic dequeue
   779  	})
   780  	defer s1.Shutdown()
   781  	codec := rpcClient(t, s1)
   782  	testutil.WaitForLeader(t, s1.RPC)
   783  	assert := assert.New(t)
   784  	state := s1.fsm.State()
   785  
   786  	// Create the original job
   787  	j := mock.Job()
   788  	j.Stable = true
   789  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   790  	j.TaskGroups[0].Update.MaxParallel = 2
   791  	j.TaskGroups[0].Update.AutoRevert = true
   792  	assert.Nil(state.UpsertJob(998, j), "UpsertJob")
   793  
   794  	// Create the second job, deployment and alloc
   795  	j2 := j.Copy()
   796  	j2.Stable = false
   797  	// Modify the job to make its specification different
   798  	j2.Meta["foo"] = "bar"
   799  	d := mock.Deployment()
   800  	d.TaskGroups["web"].AutoRevert = true
   801  	d.JobID = j2.ID
   802  	d.JobVersion = j2.Version
   803  
   804  	a := mock.Alloc()
   805  	a.JobID = j.ID
   806  	a.DeploymentID = d.ID
   807  
   808  	assert.Nil(state.UpsertJob(999, j2), "UpsertJob")
   809  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   810  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   811  
   812  	// Set the alloc as unhealthy
   813  	req := &structs.DeploymentAllocHealthRequest{
   814  		DeploymentID:           d.ID,
   815  		UnhealthyAllocationIDs: []string{a.ID},
   816  		WriteRequest:           structs.WriteRequest{Region: "global"},
   817  	}
   818  
   819  	// Fetch the response
   820  	var resp structs.DeploymentUpdateResponse
   821  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp), "RPC")
   822  	assert.NotZero(resp.Index, "bad response index")
   823  	assert.NotNil(resp.RevertedJobVersion, "bad revert version")
   824  	assert.EqualValues(0, *resp.RevertedJobVersion, "bad revert version")
   825  
   826  	// Lookup the evaluation
   827  	ws := memdb.NewWatchSet()
   828  	eval, err := state.EvalByID(ws, resp.EvalID)
   829  	assert.Nil(err, "EvalByID failed")
   830  	assert.NotNil(eval, "Expect eval")
   831  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   832  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   833  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   834  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   835  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   836  
   837  	// Lookup the deployment
   838  	expectedDesc := structs.DeploymentStatusDescriptionRollback(structs.DeploymentStatusDescriptionFailedAllocations, 0)
   839  	dout, err := state.DeploymentByID(ws, d.ID)
   840  	assert.Nil(err, "DeploymentByID failed")
   841  	assert.Equal(dout.Status, structs.DeploymentStatusFailed, "wrong status")
   842  	assert.Equal(dout.StatusDescription, expectedDesc, "wrong status description")
   843  	assert.Equal(resp.DeploymentModifyIndex, dout.ModifyIndex, "wrong modify index")
   844  	assert.Len(dout.TaskGroups, 1, "should have one group")
   845  	assert.Contains(dout.TaskGroups, "web", "should have web group")
   846  	assert.Equal(1, dout.TaskGroups["web"].UnhealthyAllocs, "should have one healthy")
   847  
   848  	// Lookup the allocation
   849  	aout, err := state.AllocByID(ws, a.ID)
   850  	assert.Nil(err, "AllocByID")
   851  	assert.NotNil(aout, "alloc")
   852  	assert.NotNil(aout.DeploymentStatus, "alloc deployment status")
   853  	assert.NotNil(aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   854  	assert.False(*aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   855  
   856  	// Lookup the job
   857  	jout, err := state.JobByID(ws, j.Namespace, j.ID)
   858  	assert.Nil(err, "JobByID")
   859  	assert.NotNil(jout, "job")
   860  	assert.EqualValues(2, jout.Version, "reverted job version")
   861  }
   862  
   863  // tests rollback upon alloc health failure to job with identical spec does not succeed
   864  func TestDeploymentEndpoint_SetAllocHealth_NoRollback(t *testing.T) {
   865  	t.Parallel()
   866  	s1 := TestServer(t, func(c *Config) {
   867  		c.NumSchedulers = 0 // Prevent automatic dequeue
   868  	})
   869  	defer s1.Shutdown()
   870  	codec := rpcClient(t, s1)
   871  	testutil.WaitForLeader(t, s1.RPC)
   872  	assert := assert.New(t)
   873  	state := s1.fsm.State()
   874  
   875  	// Create the original job
   876  	j := mock.Job()
   877  	j.Stable = true
   878  	j.TaskGroups[0].Update = structs.DefaultUpdateStrategy.Copy()
   879  	j.TaskGroups[0].Update.MaxParallel = 2
   880  	j.TaskGroups[0].Update.AutoRevert = true
   881  	assert.Nil(state.UpsertJob(998, j), "UpsertJob")
   882  
   883  	// Create the second job, deployment and alloc. Job has same spec as original
   884  	j2 := j.Copy()
   885  	j2.Stable = false
   886  
   887  	d := mock.Deployment()
   888  	d.TaskGroups["web"].AutoRevert = true
   889  	d.JobID = j2.ID
   890  	d.JobVersion = j2.Version
   891  
   892  	a := mock.Alloc()
   893  	a.JobID = j.ID
   894  	a.DeploymentID = d.ID
   895  
   896  	assert.Nil(state.UpsertJob(999, j2), "UpsertJob")
   897  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   898  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
   899  
   900  	// Set the alloc as unhealthy
   901  	req := &structs.DeploymentAllocHealthRequest{
   902  		DeploymentID:           d.ID,
   903  		UnhealthyAllocationIDs: []string{a.ID},
   904  		WriteRequest:           structs.WriteRequest{Region: "global"},
   905  	}
   906  
   907  	// Fetch the response
   908  	var resp structs.DeploymentUpdateResponse
   909  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.SetAllocHealth", req, &resp), "RPC")
   910  	assert.NotZero(resp.Index, "bad response index")
   911  	assert.Nil(resp.RevertedJobVersion, "revert version must be nil")
   912  
   913  	// Lookup the evaluation
   914  	ws := memdb.NewWatchSet()
   915  	eval, err := state.EvalByID(ws, resp.EvalID)
   916  	assert.Nil(err, "EvalByID failed")
   917  	assert.NotNil(eval, "Expect eval")
   918  	assert.Equal(eval.CreateIndex, resp.EvalCreateIndex, "eval index mismatch")
   919  	assert.Equal(eval.TriggeredBy, structs.EvalTriggerDeploymentWatcher, "eval trigger")
   920  	assert.Equal(eval.JobID, d.JobID, "eval job id")
   921  	assert.Equal(eval.DeploymentID, d.ID, "eval deployment id")
   922  	assert.Equal(eval.Status, structs.EvalStatusPending, "eval status")
   923  
   924  	// Lookup the deployment
   925  	expectedDesc := structs.DeploymentStatusDescriptionRollbackNoop(structs.DeploymentStatusDescriptionFailedAllocations, 0)
   926  	dout, err := state.DeploymentByID(ws, d.ID)
   927  	assert.Nil(err, "DeploymentByID failed")
   928  	assert.Equal(dout.Status, structs.DeploymentStatusFailed, "wrong status")
   929  	assert.Equal(dout.StatusDescription, expectedDesc, "wrong status description")
   930  	assert.Equal(resp.DeploymentModifyIndex, dout.ModifyIndex, "wrong modify index")
   931  	assert.Len(dout.TaskGroups, 1, "should have one group")
   932  	assert.Contains(dout.TaskGroups, "web", "should have web group")
   933  	assert.Equal(1, dout.TaskGroups["web"].UnhealthyAllocs, "should have one healthy")
   934  
   935  	// Lookup the allocation
   936  	aout, err := state.AllocByID(ws, a.ID)
   937  	assert.Nil(err, "AllocByID")
   938  	assert.NotNil(aout, "alloc")
   939  	assert.NotNil(aout.DeploymentStatus, "alloc deployment status")
   940  	assert.NotNil(aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   941  	assert.False(*aout.DeploymentStatus.Healthy, "alloc deployment healthy")
   942  
   943  	// Lookup the job, its version should not have changed
   944  	jout, err := state.JobByID(ws, j.Namespace, j.ID)
   945  	assert.Nil(err, "JobByID")
   946  	assert.NotNil(jout, "job")
   947  	assert.EqualValues(1, jout.Version, "original job version")
   948  }
   949  
   950  func TestDeploymentEndpoint_List(t *testing.T) {
   951  	t.Parallel()
   952  	s1 := TestServer(t, nil)
   953  	defer s1.Shutdown()
   954  	codec := rpcClient(t, s1)
   955  	testutil.WaitForLeader(t, s1.RPC)
   956  	assert := assert.New(t)
   957  
   958  	// Create the register request
   959  	j := mock.Job()
   960  	d := mock.Deployment()
   961  	d.JobID = j.ID
   962  	state := s1.fsm.State()
   963  
   964  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
   965  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   966  
   967  	// Lookup the deployments
   968  	get := &structs.DeploymentListRequest{
   969  		QueryOptions: structs.QueryOptions{
   970  			Region:    "global",
   971  			Namespace: structs.DefaultNamespace,
   972  		},
   973  	}
   974  	var resp structs.DeploymentListResponse
   975  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
   976  	assert.EqualValues(resp.Index, 1000, "Wrong Index")
   977  	assert.Len(resp.Deployments, 1, "Deployments")
   978  	assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
   979  
   980  	// Lookup the deploys by prefix
   981  	get = &structs.DeploymentListRequest{
   982  		QueryOptions: structs.QueryOptions{
   983  			Region:    "global",
   984  			Namespace: structs.DefaultNamespace,
   985  			Prefix:    d.ID[:4],
   986  		},
   987  	}
   988  
   989  	var resp2 structs.DeploymentListResponse
   990  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp2), "RPC")
   991  	assert.EqualValues(resp.Index, 1000, "Wrong Index")
   992  	assert.Len(resp2.Deployments, 1, "Deployments")
   993  	assert.Equal(resp2.Deployments[0].ID, d.ID, "Deployment ID")
   994  }
   995  
   996  func TestDeploymentEndpoint_List_ACL(t *testing.T) {
   997  	t.Parallel()
   998  	s1, root := TestACLServer(t, nil)
   999  	defer s1.Shutdown()
  1000  	codec := rpcClient(t, s1)
  1001  	testutil.WaitForLeader(t, s1.RPC)
  1002  	assert := assert.New(t)
  1003  
  1004  	// Create the register request
  1005  	j := mock.Job()
  1006  	d := mock.Deployment()
  1007  	d.JobID = j.ID
  1008  	state := s1.fsm.State()
  1009  
  1010  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
  1011  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
  1012  
  1013  	// Create the namespace policy and tokens
  1014  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
  1015  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  1016  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  1017  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  1018  
  1019  	get := &structs.DeploymentListRequest{
  1020  		QueryOptions: structs.QueryOptions{
  1021  			Region:    "global",
  1022  			Namespace: structs.DefaultNamespace,
  1023  		},
  1024  	}
  1025  
  1026  	// Try with no token and expect permission denied
  1027  	{
  1028  		var resp structs.DeploymentUpdateResponse
  1029  		err := msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
  1030  		assert.NotNil(err)
  1031  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
  1032  	}
  1033  
  1034  	// Try with an invalid token
  1035  	{
  1036  		get.AuthToken = invalidToken.SecretID
  1037  		var resp structs.DeploymentUpdateResponse
  1038  		err := msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp)
  1039  		assert.NotNil(err)
  1040  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
  1041  	}
  1042  
  1043  	// Lookup the deployments with a root token
  1044  	{
  1045  		get.AuthToken = root.SecretID
  1046  		var resp structs.DeploymentListResponse
  1047  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
  1048  		assert.EqualValues(resp.Index, 1000, "Wrong Index")
  1049  		assert.Len(resp.Deployments, 1, "Deployments")
  1050  		assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
  1051  	}
  1052  
  1053  	// Lookup the deployments with a valid token
  1054  	{
  1055  		get.AuthToken = validToken.SecretID
  1056  		var resp structs.DeploymentListResponse
  1057  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", get, &resp), "RPC")
  1058  		assert.EqualValues(resp.Index, 1000, "Wrong Index")
  1059  		assert.Len(resp.Deployments, 1, "Deployments")
  1060  		assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
  1061  	}
  1062  }
  1063  
  1064  func TestDeploymentEndpoint_List_Blocking(t *testing.T) {
  1065  	t.Parallel()
  1066  	s1 := TestServer(t, nil)
  1067  	defer s1.Shutdown()
  1068  	state := s1.fsm.State()
  1069  	codec := rpcClient(t, s1)
  1070  	testutil.WaitForLeader(t, s1.RPC)
  1071  	assert := assert.New(t)
  1072  
  1073  	// Create the deployment
  1074  	j := mock.Job()
  1075  	d := mock.Deployment()
  1076  	d.JobID = j.ID
  1077  
  1078  	assert.Nil(state.UpsertJob(999, j), "UpsertJob")
  1079  
  1080  	// Upsert alloc triggers watches
  1081  	time.AfterFunc(100*time.Millisecond, func() {
  1082  		assert.Nil(state.UpsertDeployment(3, d), "UpsertDeployment")
  1083  	})
  1084  
  1085  	req := &structs.DeploymentListRequest{
  1086  		QueryOptions: structs.QueryOptions{
  1087  			Region:        "global",
  1088  			Namespace:     structs.DefaultNamespace,
  1089  			MinQueryIndex: 1,
  1090  		},
  1091  	}
  1092  	start := time.Now()
  1093  	var resp structs.DeploymentListResponse
  1094  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp), "RPC")
  1095  	assert.EqualValues(resp.Index, 3, "Wrong Index")
  1096  	assert.Len(resp.Deployments, 1, "Deployments")
  1097  	assert.Equal(resp.Deployments[0].ID, d.ID, "Deployment ID")
  1098  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1099  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1100  	}
  1101  
  1102  	// Deployment updates trigger watches
  1103  	d2 := d.Copy()
  1104  	d2.Status = structs.DeploymentStatusPaused
  1105  	time.AfterFunc(100*time.Millisecond, func() {
  1106  		assert.Nil(state.UpsertDeployment(5, d2), "UpsertDeployment")
  1107  	})
  1108  
  1109  	req.MinQueryIndex = 3
  1110  	start = time.Now()
  1111  	var resp2 structs.DeploymentListResponse
  1112  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.List", req, &resp2), "RPC")
  1113  	assert.EqualValues(5, resp2.Index, "Wrong Index")
  1114  	assert.Len(resp2.Deployments, 1, "Deployments")
  1115  	assert.Equal(d2.ID, resp2.Deployments[0].ID, "Deployment ID")
  1116  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1117  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1118  	}
  1119  }
  1120  
  1121  func TestDeploymentEndpoint_Allocations(t *testing.T) {
  1122  	t.Parallel()
  1123  	s1 := TestServer(t, nil)
  1124  	defer s1.Shutdown()
  1125  	codec := rpcClient(t, s1)
  1126  	testutil.WaitForLeader(t, s1.RPC)
  1127  	assert := assert.New(t)
  1128  
  1129  	// Create the register request
  1130  	j := mock.Job()
  1131  	d := mock.Deployment()
  1132  	d.JobID = j.ID
  1133  	a := mock.Alloc()
  1134  	a.DeploymentID = d.ID
  1135  	summary := mock.JobSummary(a.JobID)
  1136  	state := s1.fsm.State()
  1137  
  1138  	assert.Nil(state.UpsertJob(998, j), "UpsertJob")
  1139  	assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary")
  1140  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
  1141  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
  1142  
  1143  	// Lookup the allocations
  1144  	get := &structs.DeploymentSpecificRequest{
  1145  		DeploymentID: d.ID,
  1146  		QueryOptions: structs.QueryOptions{
  1147  			Region:    "global",
  1148  			Namespace: structs.DefaultNamespace,
  1149  		},
  1150  	}
  1151  	var resp structs.AllocListResponse
  1152  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", get, &resp), "RPC")
  1153  	assert.EqualValues(1001, resp.Index, "Wrong Index")
  1154  	assert.Len(resp.Allocations, 1, "Allocations")
  1155  	assert.Equal(a.ID, resp.Allocations[0].ID, "Allocation ID")
  1156  }
  1157  
  1158  func TestDeploymentEndpoint_Allocations_ACL(t *testing.T) {
  1159  	t.Parallel()
  1160  	s1, root := TestACLServer(t, nil)
  1161  	defer s1.Shutdown()
  1162  	codec := rpcClient(t, s1)
  1163  	testutil.WaitForLeader(t, s1.RPC)
  1164  	assert := assert.New(t)
  1165  
  1166  	// Create the register request
  1167  	j := mock.Job()
  1168  	d := mock.Deployment()
  1169  	d.JobID = j.ID
  1170  	a := mock.Alloc()
  1171  	a.DeploymentID = d.ID
  1172  	summary := mock.JobSummary(a.JobID)
  1173  	state := s1.fsm.State()
  1174  
  1175  	assert.Nil(state.UpsertJob(998, j), "UpsertJob")
  1176  	assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary")
  1177  	assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
  1178  	assert.Nil(state.UpsertAllocs(1001, []*structs.Allocation{a}), "UpsertAllocs")
  1179  
  1180  	// Create the namespace policy and tokens
  1181  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
  1182  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  1183  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  1184  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  1185  
  1186  	get := &structs.DeploymentSpecificRequest{
  1187  		DeploymentID: d.ID,
  1188  		QueryOptions: structs.QueryOptions{
  1189  			Region:    "global",
  1190  			Namespace: structs.DefaultNamespace,
  1191  		},
  1192  	}
  1193  
  1194  	// Try with no token and expect permission denied
  1195  	{
  1196  		var resp structs.DeploymentUpdateResponse
  1197  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", get, &resp)
  1198  		assert.NotNil(err)
  1199  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
  1200  	}
  1201  
  1202  	// Try with an invalid token
  1203  	{
  1204  		get.AuthToken = invalidToken.SecretID
  1205  		var resp structs.DeploymentUpdateResponse
  1206  		err := msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", get, &resp)
  1207  		assert.NotNil(err)
  1208  		assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
  1209  	}
  1210  
  1211  	// Lookup the allocations with a valid token
  1212  	{
  1213  		get.AuthToken = validToken.SecretID
  1214  		var resp structs.AllocListResponse
  1215  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", get, &resp), "RPC")
  1216  		assert.EqualValues(1001, resp.Index, "Wrong Index")
  1217  		assert.Len(resp.Allocations, 1, "Allocations")
  1218  		assert.Equal(a.ID, resp.Allocations[0].ID, "Allocation ID")
  1219  	}
  1220  
  1221  	// Lookup the allocations with a root token
  1222  	{
  1223  		get.AuthToken = root.SecretID
  1224  		var resp structs.AllocListResponse
  1225  		assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", get, &resp), "RPC")
  1226  		assert.EqualValues(1001, resp.Index, "Wrong Index")
  1227  		assert.Len(resp.Allocations, 1, "Allocations")
  1228  		assert.Equal(a.ID, resp.Allocations[0].ID, "Allocation ID")
  1229  	}
  1230  }
  1231  
  1232  func TestDeploymentEndpoint_Allocations_Blocking(t *testing.T) {
  1233  	t.Parallel()
  1234  	s1 := TestServer(t, nil)
  1235  	defer s1.Shutdown()
  1236  	state := s1.fsm.State()
  1237  	codec := rpcClient(t, s1)
  1238  	testutil.WaitForLeader(t, s1.RPC)
  1239  	assert := assert.New(t)
  1240  
  1241  	// Create the alloc
  1242  	j := mock.Job()
  1243  	d := mock.Deployment()
  1244  	d.JobID = j.ID
  1245  	a := mock.Alloc()
  1246  	a.DeploymentID = d.ID
  1247  	summary := mock.JobSummary(a.JobID)
  1248  
  1249  	assert.Nil(state.UpsertJob(1, j), "UpsertJob")
  1250  	assert.Nil(state.UpsertDeployment(2, d), "UpsertDeployment")
  1251  	assert.Nil(state.UpsertJobSummary(3, summary), "UpsertJobSummary")
  1252  
  1253  	// Upsert alloc triggers watches
  1254  	time.AfterFunc(100*time.Millisecond, func() {
  1255  		assert.Nil(state.UpsertAllocs(4, []*structs.Allocation{a}), "UpsertAllocs")
  1256  	})
  1257  
  1258  	req := &structs.DeploymentSpecificRequest{
  1259  		DeploymentID: d.ID,
  1260  		QueryOptions: structs.QueryOptions{
  1261  			Region:        "global",
  1262  			Namespace:     structs.DefaultNamespace,
  1263  			MinQueryIndex: 1,
  1264  		},
  1265  	}
  1266  	start := time.Now()
  1267  	var resp structs.AllocListResponse
  1268  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", req, &resp), "RPC")
  1269  	assert.EqualValues(4, resp.Index, "Wrong Index")
  1270  	assert.Len(resp.Allocations, 1, "Allocations")
  1271  	assert.Equal(a.ID, resp.Allocations[0].ID, "Allocation ID")
  1272  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1273  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1274  	}
  1275  
  1276  	// Client updates trigger watches
  1277  	a2 := mock.Alloc()
  1278  	a2.ID = a.ID
  1279  	a2.DeploymentID = a.DeploymentID
  1280  	a2.ClientStatus = structs.AllocClientStatusRunning
  1281  	time.AfterFunc(100*time.Millisecond, func() {
  1282  		assert.Nil(state.UpsertJobSummary(5, mock.JobSummary(a2.JobID)), "UpsertJobSummary")
  1283  		assert.Nil(state.UpdateAllocsFromClient(6, []*structs.Allocation{a2}), "updateAllocsFromClient")
  1284  	})
  1285  
  1286  	req.MinQueryIndex = 4
  1287  	start = time.Now()
  1288  	var resp2 structs.AllocListResponse
  1289  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Allocations", req, &resp2), "RPC")
  1290  	assert.EqualValues(6, resp2.Index, "Wrong Index")
  1291  	assert.Len(resp2.Allocations, 1, "Allocations")
  1292  	assert.Equal(a.ID, resp2.Allocations[0].ID, "Allocation ID")
  1293  	assert.Equal(structs.AllocClientStatusRunning, resp2.Allocations[0].ClientStatus, "Client Status")
  1294  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1295  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1296  	}
  1297  }
  1298  
  1299  func TestDeploymentEndpoint_Reap(t *testing.T) {
  1300  	t.Parallel()
  1301  	s1 := TestServer(t, nil)
  1302  	defer s1.Shutdown()
  1303  	codec := rpcClient(t, s1)
  1304  	testutil.WaitForLeader(t, s1.RPC)
  1305  	assert := assert.New(t)
  1306  
  1307  	// Create the register request
  1308  	d1 := mock.Deployment()
  1309  	assert.Nil(s1.fsm.State().UpsertDeployment(1000, d1), "UpsertDeployment")
  1310  
  1311  	// Reap the eval
  1312  	get := &structs.DeploymentDeleteRequest{
  1313  		Deployments:  []string{d1.ID},
  1314  		WriteRequest: structs.WriteRequest{Region: "global"},
  1315  	}
  1316  	var resp structs.GenericResponse
  1317  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Deployment.Reap", get, &resp), "RPC")
  1318  	assert.NotZero(resp.Index, "bad response index")
  1319  
  1320  	// Ensure deleted
  1321  	ws := memdb.NewWatchSet()
  1322  	outD, err := s1.fsm.State().DeploymentByID(ws, d1.ID)
  1323  	assert.Nil(err, "DeploymentByID")
  1324  	assert.Nil(outD, "Deleted Deployment")
  1325  }