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 }