github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/deployment_endpoint.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "time" 6 7 metrics "github.com/armon/go-metrics" 8 log "github.com/hashicorp/go-hclog" 9 memdb "github.com/hashicorp/go-memdb" 10 11 "github.com/hashicorp/nomad/acl" 12 "github.com/hashicorp/nomad/nomad/state" 13 "github.com/hashicorp/nomad/nomad/structs" 14 ) 15 16 // Deployment endpoint is used for manipulating deployments 17 type Deployment struct { 18 srv *Server 19 logger log.Logger 20 } 21 22 // GetDeployment is used to request information about a specific deployment 23 func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest, 24 reply *structs.SingleDeploymentResponse) error { 25 if done, err := d.srv.forward("Deployment.GetDeployment", args, args, reply); done { 26 return err 27 } 28 defer metrics.MeasureSince([]string{"nomad", "deployment", "get_deployment"}, time.Now()) 29 30 // Check namespace read-job permissions 31 allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) 32 aclObj, err := d.srv.ResolveToken(args.AuthToken) 33 if err != nil { 34 return err 35 } else if !allowNsOp(aclObj, args.RequestNamespace()) { 36 return structs.ErrPermissionDenied 37 } 38 39 // Setup the blocking query 40 opts := blockingOptions{ 41 queryOpts: &args.QueryOptions, 42 queryMeta: &reply.QueryMeta, 43 run: func(ws memdb.WatchSet, state *state.StateStore) error { 44 // Verify the arguments 45 if args.DeploymentID == "" { 46 return fmt.Errorf("missing deployment ID") 47 } 48 49 // Look for the deployment 50 out, err := state.DeploymentByID(ws, args.DeploymentID) 51 if err != nil { 52 return err 53 } 54 55 // Re-check namespace in case it differs from request. 56 if out != nil && !allowNsOp(aclObj, out.Namespace) { 57 // hide this deployment, caller is not authorized to view it 58 out = nil 59 } 60 61 // Setup the output 62 reply.Deployment = out 63 if out != nil { 64 reply.Index = out.ModifyIndex 65 } else { 66 // Use the last index that affected the deployments table 67 index, err := state.Index("deployment") 68 if err != nil { 69 return err 70 } 71 reply.Index = index 72 } 73 74 // Set the query response 75 d.srv.setQueryMeta(&reply.QueryMeta) 76 return nil 77 }} 78 return d.srv.blockingRPC(&opts) 79 } 80 81 // Fail is used to force fail a deployment 82 func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.DeploymentUpdateResponse) error { 83 if done, err := d.srv.forward("Deployment.Fail", args, args, reply); done { 84 return err 85 } 86 defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now()) 87 88 // Validate the arguments 89 if args.DeploymentID == "" { 90 return fmt.Errorf("missing deployment ID") 91 } 92 93 // Lookup the deployment 94 snap, err := d.srv.fsm.State().Snapshot() 95 if err != nil { 96 return err 97 } 98 99 ws := memdb.NewWatchSet() 100 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 101 if err != nil { 102 return err 103 } 104 if deploy == nil { 105 return fmt.Errorf("deployment not found") 106 } 107 108 // Check namespace submit-job permissions 109 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 110 return err 111 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 112 return structs.ErrPermissionDenied 113 } 114 115 if !deploy.Active() { 116 return structs.ErrDeploymentTerminalNoFail 117 } 118 119 // Call into the deployment watcher 120 return d.srv.deploymentWatcher.FailDeployment(args, reply) 121 } 122 123 // Pause is used to pause a deployment 124 func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.DeploymentUpdateResponse) error { 125 if done, err := d.srv.forward("Deployment.Pause", args, args, reply); done { 126 return err 127 } 128 defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now()) 129 130 // Validate the arguments 131 if args.DeploymentID == "" { 132 return fmt.Errorf("missing deployment ID") 133 } 134 135 // Lookup the deployment 136 snap, err := d.srv.fsm.State().Snapshot() 137 if err != nil { 138 return err 139 } 140 141 ws := memdb.NewWatchSet() 142 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 143 if err != nil { 144 return err 145 } 146 if deploy == nil { 147 return fmt.Errorf("deployment not found") 148 } 149 150 // Check namespace submit-job permissions 151 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 152 return err 153 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 154 return structs.ErrPermissionDenied 155 } 156 157 if !deploy.Active() { 158 if args.Pause { 159 return structs.ErrDeploymentTerminalNoPause 160 } 161 162 return structs.ErrDeploymentTerminalNoResume 163 } 164 165 // Call into the deployment watcher 166 return d.srv.deploymentWatcher.PauseDeployment(args, reply) 167 } 168 169 // Promote is used to promote canaries in a deployment 170 func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *structs.DeploymentUpdateResponse) error { 171 if done, err := d.srv.forward("Deployment.Promote", args, args, reply); done { 172 return err 173 } 174 defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now()) 175 176 // Validate the arguments 177 if args.DeploymentID == "" { 178 return fmt.Errorf("missing deployment ID") 179 } 180 181 // Lookup the deployment 182 snap, err := d.srv.fsm.State().Snapshot() 183 if err != nil { 184 return err 185 } 186 187 ws := memdb.NewWatchSet() 188 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 189 if err != nil { 190 return err 191 } 192 if deploy == nil { 193 return fmt.Errorf("deployment not found") 194 } 195 196 // Check namespace submit-job permissions 197 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 198 return err 199 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 200 return structs.ErrPermissionDenied 201 } 202 203 if !deploy.Active() { 204 return structs.ErrDeploymentTerminalNoPromote 205 } 206 207 // Call into the deployment watcher 208 return d.srv.deploymentWatcher.PromoteDeployment(args, reply) 209 } 210 211 // Run is used to start a pending deployment 212 func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.DeploymentUpdateResponse) error { 213 if done, err := d.srv.forward("Deployment.Run", args, args, reply); done { 214 return err 215 } 216 defer metrics.MeasureSince([]string{"nomad", "deployment", "run"}, time.Now()) 217 218 // Validate the arguments 219 if args.DeploymentID == "" { 220 return fmt.Errorf("missing deployment ID") 221 } 222 223 // Lookup the deployment 224 snap, err := d.srv.fsm.State().Snapshot() 225 if err != nil { 226 return err 227 } 228 229 ws := memdb.NewWatchSet() 230 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 231 if err != nil { 232 return err 233 } 234 if deploy == nil { 235 return fmt.Errorf("deployment not found") 236 } 237 238 // Check namespace submit-job permissions 239 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 240 return err 241 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 242 return structs.ErrPermissionDenied 243 } 244 245 if !deploy.Active() { 246 return structs.ErrDeploymentTerminalNoRun 247 } 248 249 // Call into the deployment watcher 250 return d.srv.deploymentWatcher.RunDeployment(args, reply) 251 } 252 253 // Unblock is used to unblock a deployment 254 func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *structs.DeploymentUpdateResponse) error { 255 if done, err := d.srv.forward("Deployment.Unblock", args, args, reply); done { 256 return err 257 } 258 defer metrics.MeasureSince([]string{"nomad", "deployment", "unblock"}, time.Now()) 259 260 // Validate the arguments 261 if args.DeploymentID == "" { 262 return fmt.Errorf("missing deployment ID") 263 } 264 265 // Lookup the deployment 266 snap, err := d.srv.fsm.State().Snapshot() 267 if err != nil { 268 return err 269 } 270 271 ws := memdb.NewWatchSet() 272 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 273 if err != nil { 274 return err 275 } 276 if deploy == nil { 277 return fmt.Errorf("deployment not found") 278 } 279 280 // Check namespace submit-job permissions 281 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 282 return err 283 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 284 return structs.ErrPermissionDenied 285 } 286 287 if !deploy.Active() { 288 return structs.ErrDeploymentTerminalNoUnblock 289 } 290 291 // Call into the deployment watcher 292 return d.srv.deploymentWatcher.UnblockDeployment(args, reply) 293 } 294 295 // Cancel is used to cancel a deployment 296 func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *structs.DeploymentUpdateResponse) error { 297 if done, err := d.srv.forward("Deployment.Cancel", args, args, reply); done { 298 return err 299 } 300 defer metrics.MeasureSince([]string{"nomad", "deployment", "cancel"}, time.Now()) 301 302 // Validate the arguments 303 if args.DeploymentID == "" { 304 return fmt.Errorf("missing deployment ID") 305 } 306 307 // Lookup the deployment 308 snap, err := d.srv.fsm.State().Snapshot() 309 if err != nil { 310 return err 311 } 312 313 ws := memdb.NewWatchSet() 314 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 315 if err != nil { 316 return err 317 } 318 if deploy == nil { 319 return fmt.Errorf("deployment not found") 320 } 321 322 // Check namespace submit-job permissions 323 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 324 return err 325 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 326 return structs.ErrPermissionDenied 327 } 328 329 if !deploy.Active() { 330 return structs.ErrDeploymentTerminalNoCancel 331 } 332 333 // Call into the deployment watcher 334 return d.srv.deploymentWatcher.CancelDeployment(args, reply) 335 } 336 337 // SetAllocHealth is used to set the health of allocations that are part of the 338 // deployment. 339 func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, reply *structs.DeploymentUpdateResponse) error { 340 if done, err := d.srv.forward("Deployment.SetAllocHealth", args, args, reply); done { 341 return err 342 } 343 defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now()) 344 345 // Validate the arguments 346 if args.DeploymentID == "" { 347 return fmt.Errorf("missing deployment ID") 348 } 349 350 if len(args.HealthyAllocationIDs)+len(args.UnhealthyAllocationIDs) == 0 { 351 return fmt.Errorf("must specify at least one healthy/unhealthy allocation ID") 352 } 353 354 // Lookup the deployment 355 snap, err := d.srv.fsm.State().Snapshot() 356 if err != nil { 357 return err 358 } 359 360 ws := memdb.NewWatchSet() 361 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 362 if err != nil { 363 return err 364 } 365 if deploy == nil { 366 return fmt.Errorf("deployment not found") 367 } 368 369 // Check namespace submit-job permissions 370 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 371 return err 372 } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { 373 return structs.ErrPermissionDenied 374 } 375 376 if !deploy.Active() { 377 return structs.ErrDeploymentTerminalNoSetHealth 378 } 379 380 // Call into the deployment watcher 381 return d.srv.deploymentWatcher.SetAllocHealth(args, reply) 382 } 383 384 // List returns the list of deployments in the system 385 func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error { 386 if done, err := d.srv.forward("Deployment.List", args, args, reply); done { 387 return err 388 } 389 defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now()) 390 391 // Check namespace read-job permissions against request namespace since 392 // results are filtered by request namespace. 393 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 394 return err 395 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { 396 return structs.ErrPermissionDenied 397 } 398 399 // Setup the blocking query 400 opts := blockingOptions{ 401 queryOpts: &args.QueryOptions, 402 queryMeta: &reply.QueryMeta, 403 run: func(ws memdb.WatchSet, state *state.StateStore) error { 404 // Capture all the deployments 405 var err error 406 var iter memdb.ResultIterator 407 if prefix := args.QueryOptions.Prefix; prefix != "" { 408 iter, err = state.DeploymentsByIDPrefix(ws, args.RequestNamespace(), prefix) 409 } else { 410 iter, err = state.DeploymentsByNamespace(ws, args.RequestNamespace()) 411 } 412 if err != nil { 413 return err 414 } 415 416 var deploys []*structs.Deployment 417 for { 418 raw := iter.Next() 419 if raw == nil { 420 break 421 } 422 deploy := raw.(*structs.Deployment) 423 deploys = append(deploys, deploy) 424 } 425 reply.Deployments = deploys 426 427 // Use the last index that affected the deployment table 428 index, err := state.Index("deployment") 429 if err != nil { 430 return err 431 } 432 reply.Index = index 433 434 // Set the query response 435 d.srv.setQueryMeta(&reply.QueryMeta) 436 return nil 437 }} 438 return d.srv.blockingRPC(&opts) 439 } 440 441 // Allocations returns the list of allocations that are a part of the deployment 442 func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error { 443 if done, err := d.srv.forward("Deployment.Allocations", args, args, reply); done { 444 return err 445 } 446 defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now()) 447 448 // Check namespace read-job permissions against the request namespace. 449 // Must re-check against the alloc namespace when they return to ensure 450 // there's no namespace mismatch. 451 allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) 452 aclObj, err := d.srv.ResolveToken(args.AuthToken) 453 if err != nil { 454 return err 455 } else if !allowNsOp(aclObj, args.RequestNamespace()) { 456 return structs.ErrPermissionDenied 457 } 458 459 // Setup the blocking query 460 opts := blockingOptions{ 461 queryOpts: &args.QueryOptions, 462 queryMeta: &reply.QueryMeta, 463 run: func(ws memdb.WatchSet, state *state.StateStore) error { 464 // Capture all the allocations 465 allocs, err := state.AllocsByDeployment(ws, args.DeploymentID) 466 if err != nil { 467 return err 468 } 469 470 // Deployments do not span namespaces so just check the 471 // first allocs namespace. 472 if len(allocs) > 0 { 473 ns := allocs[0].Namespace 474 if ns != args.RequestNamespace() && !allowNsOp(aclObj, ns) { 475 return structs.ErrPermissionDenied 476 } 477 } 478 479 stubs := make([]*structs.AllocListStub, 0, len(allocs)) 480 for _, alloc := range allocs { 481 stubs = append(stubs, alloc.Stub(nil)) 482 } 483 reply.Allocations = stubs 484 485 // Use the last index that affected the jobs table 486 index, err := state.Index("allocs") 487 if err != nil { 488 return err 489 } 490 reply.Index = index 491 492 // Set the query response 493 d.srv.setQueryMeta(&reply.QueryMeta) 494 return nil 495 }} 496 return d.srv.blockingRPC(&opts) 497 } 498 499 // Reap is used to cleanup terminal deployments 500 func (d *Deployment) Reap(args *structs.DeploymentDeleteRequest, 501 reply *structs.GenericResponse) error { 502 if done, err := d.srv.forward("Deployment.Reap", args, args, reply); done { 503 return err 504 } 505 defer metrics.MeasureSince([]string{"nomad", "deployment", "reap"}, time.Now()) 506 507 // Update via Raft 508 _, index, err := d.srv.raftApply(structs.DeploymentDeleteRequestType, args) 509 if err != nil { 510 return err 511 } 512 513 // Update the index 514 reply.Index = index 515 return nil 516 }