github.com/bigcommerce/nomad@v0.9.3-bc/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 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 32 return err 33 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { 34 return structs.ErrPermissionDenied 35 } 36 37 // Setup the blocking query 38 opts := blockingOptions{ 39 queryOpts: &args.QueryOptions, 40 queryMeta: &reply.QueryMeta, 41 run: func(ws memdb.WatchSet, state *state.StateStore) error { 42 // Verify the arguments 43 if args.DeploymentID == "" { 44 return fmt.Errorf("missing deployment ID") 45 } 46 47 // Look for the deployment 48 out, err := state.DeploymentByID(ws, args.DeploymentID) 49 if err != nil { 50 return err 51 } 52 53 // Setup the output 54 reply.Deployment = out 55 if out != nil { 56 reply.Index = out.ModifyIndex 57 } else { 58 // Use the last index that affected the deployments table 59 index, err := state.Index("deployment") 60 if err != nil { 61 return err 62 } 63 reply.Index = index 64 } 65 66 // Set the query response 67 d.srv.setQueryMeta(&reply.QueryMeta) 68 return nil 69 }} 70 return d.srv.blockingRPC(&opts) 71 } 72 73 // Fail is used to force fail a deployment 74 func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.DeploymentUpdateResponse) error { 75 if done, err := d.srv.forward("Deployment.Fail", args, args, reply); done { 76 return err 77 } 78 defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now()) 79 80 // Check namespace submit-job permissions 81 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 82 return err 83 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { 84 return structs.ErrPermissionDenied 85 } 86 87 // Validate the arguments 88 if args.DeploymentID == "" { 89 return fmt.Errorf("missing deployment ID") 90 } 91 92 // Lookup the deployment 93 snap, err := d.srv.fsm.State().Snapshot() 94 if err != nil { 95 return err 96 } 97 98 ws := memdb.NewWatchSet() 99 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 100 if err != nil { 101 return err 102 } 103 if deploy == nil { 104 return fmt.Errorf("deployment not found") 105 } 106 107 if !deploy.Active() { 108 return fmt.Errorf("can't fail terminal deployment") 109 } 110 111 // Call into the deployment watcher 112 return d.srv.deploymentWatcher.FailDeployment(args, reply) 113 } 114 115 // Pause is used to pause a deployment 116 func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.DeploymentUpdateResponse) error { 117 if done, err := d.srv.forward("Deployment.Pause", args, args, reply); done { 118 return err 119 } 120 defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now()) 121 122 // Check namespace submit-job permissions 123 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 124 return err 125 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { 126 return structs.ErrPermissionDenied 127 } 128 129 // Validate the arguments 130 if args.DeploymentID == "" { 131 return fmt.Errorf("missing deployment ID") 132 } 133 134 // Lookup the deployment 135 snap, err := d.srv.fsm.State().Snapshot() 136 if err != nil { 137 return err 138 } 139 140 ws := memdb.NewWatchSet() 141 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 142 if err != nil { 143 return err 144 } 145 if deploy == nil { 146 return fmt.Errorf("deployment not found") 147 } 148 149 if !deploy.Active() { 150 if args.Pause { 151 return fmt.Errorf("can't pause terminal deployment") 152 } 153 154 return fmt.Errorf("can't resume terminal deployment") 155 } 156 157 // Call into the deployment watcher 158 return d.srv.deploymentWatcher.PauseDeployment(args, reply) 159 } 160 161 // Promote is used to promote canaries in a deployment 162 func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *structs.DeploymentUpdateResponse) error { 163 if done, err := d.srv.forward("Deployment.Promote", args, args, reply); done { 164 return err 165 } 166 defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now()) 167 168 // Check namespace submit-job permissions 169 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 170 return err 171 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { 172 return structs.ErrPermissionDenied 173 } 174 175 // Validate the arguments 176 if args.DeploymentID == "" { 177 return fmt.Errorf("missing deployment ID") 178 } 179 180 // Lookup the deployment 181 snap, err := d.srv.fsm.State().Snapshot() 182 if err != nil { 183 return err 184 } 185 186 ws := memdb.NewWatchSet() 187 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 188 if err != nil { 189 return err 190 } 191 if deploy == nil { 192 return fmt.Errorf("deployment not found") 193 } 194 195 if !deploy.Active() { 196 return fmt.Errorf("can't promote terminal deployment") 197 } 198 199 // Call into the deployment watcher 200 return d.srv.deploymentWatcher.PromoteDeployment(args, reply) 201 } 202 203 // SetAllocHealth is used to set the health of allocations that are part of the 204 // deployment. 205 func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, reply *structs.DeploymentUpdateResponse) error { 206 if done, err := d.srv.forward("Deployment.SetAllocHealth", args, args, reply); done { 207 return err 208 } 209 defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now()) 210 211 // Check namespace submit-job permissions 212 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 213 return err 214 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { 215 return structs.ErrPermissionDenied 216 } 217 218 // Validate the arguments 219 if args.DeploymentID == "" { 220 return fmt.Errorf("missing deployment ID") 221 } 222 223 if len(args.HealthyAllocationIDs)+len(args.UnhealthyAllocationIDs) == 0 { 224 return fmt.Errorf("must specify at least one healthy/unhealthy allocation ID") 225 } 226 227 // Lookup the deployment 228 snap, err := d.srv.fsm.State().Snapshot() 229 if err != nil { 230 return err 231 } 232 233 ws := memdb.NewWatchSet() 234 deploy, err := snap.DeploymentByID(ws, args.DeploymentID) 235 if err != nil { 236 return err 237 } 238 if deploy == nil { 239 return fmt.Errorf("deployment not found") 240 } 241 242 if !deploy.Active() { 243 return fmt.Errorf("can't set health of allocations for a terminal deployment") 244 } 245 246 // Call into the deployment watcher 247 return d.srv.deploymentWatcher.SetAllocHealth(args, reply) 248 } 249 250 // List returns the list of deployments in the system 251 func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error { 252 if done, err := d.srv.forward("Deployment.List", args, args, reply); done { 253 return err 254 } 255 defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now()) 256 257 // Check namespace read-job permissions 258 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 259 return err 260 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { 261 return structs.ErrPermissionDenied 262 } 263 264 // Setup the blocking query 265 opts := blockingOptions{ 266 queryOpts: &args.QueryOptions, 267 queryMeta: &reply.QueryMeta, 268 run: func(ws memdb.WatchSet, state *state.StateStore) error { 269 // Capture all the deployments 270 var err error 271 var iter memdb.ResultIterator 272 if prefix := args.QueryOptions.Prefix; prefix != "" { 273 iter, err = state.DeploymentsByIDPrefix(ws, args.RequestNamespace(), prefix) 274 } else { 275 iter, err = state.DeploymentsByNamespace(ws, args.RequestNamespace()) 276 } 277 if err != nil { 278 return err 279 } 280 281 var deploys []*structs.Deployment 282 for { 283 raw := iter.Next() 284 if raw == nil { 285 break 286 } 287 deploy := raw.(*structs.Deployment) 288 deploys = append(deploys, deploy) 289 } 290 reply.Deployments = deploys 291 292 // Use the last index that affected the deployment table 293 index, err := state.Index("deployment") 294 if err != nil { 295 return err 296 } 297 reply.Index = index 298 299 // Set the query response 300 d.srv.setQueryMeta(&reply.QueryMeta) 301 return nil 302 }} 303 return d.srv.blockingRPC(&opts) 304 } 305 306 // Allocations returns the list of allocations that are a part of the deployment 307 func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error { 308 if done, err := d.srv.forward("Deployment.Allocations", args, args, reply); done { 309 return err 310 } 311 defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now()) 312 313 // Check namespace read-job permissions 314 if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { 315 return err 316 } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { 317 return structs.ErrPermissionDenied 318 } 319 320 // Setup the blocking query 321 opts := blockingOptions{ 322 queryOpts: &args.QueryOptions, 323 queryMeta: &reply.QueryMeta, 324 run: func(ws memdb.WatchSet, state *state.StateStore) error { 325 // Capture all the allocations 326 allocs, err := state.AllocsByDeployment(ws, args.DeploymentID) 327 if err != nil { 328 return err 329 } 330 331 stubs := make([]*structs.AllocListStub, 0, len(allocs)) 332 for _, alloc := range allocs { 333 stubs = append(stubs, alloc.Stub()) 334 } 335 reply.Allocations = stubs 336 337 // Use the last index that affected the jobs table 338 index, err := state.Index("allocs") 339 if err != nil { 340 return err 341 } 342 reply.Index = index 343 344 // Set the query response 345 d.srv.setQueryMeta(&reply.QueryMeta) 346 return nil 347 }} 348 return d.srv.blockingRPC(&opts) 349 } 350 351 // Reap is used to cleanup terminal deployments 352 func (d *Deployment) Reap(args *structs.DeploymentDeleteRequest, 353 reply *structs.GenericResponse) error { 354 if done, err := d.srv.forward("Deployment.Reap", args, args, reply); done { 355 return err 356 } 357 defer metrics.MeasureSince([]string{"nomad", "deployment", "reap"}, time.Now()) 358 359 // Update via Raft 360 _, index, err := d.srv.raftApply(structs.DeploymentDeleteRequestType, args) 361 if err != nil { 362 return err 363 } 364 365 // Update the index 366 reply.Index = index 367 return nil 368 }