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