github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/command/agent/job_endpoint.go (about) 1 package agent 2 3 import ( 4 "net/http" 5 "strconv" 6 "strings" 7 8 "github.com/golang/snappy" 9 "github.com/ncodes/nomad/api" 10 "github.com/ncodes/nomad/nomad/structs" 11 ) 12 13 func (s *HTTPServer) JobsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 14 switch req.Method { 15 case "GET": 16 return s.jobListRequest(resp, req) 17 case "PUT", "POST": 18 return s.jobUpdate(resp, req, "") 19 default: 20 return nil, CodedError(405, ErrInvalidMethod) 21 } 22 } 23 24 func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 25 args := structs.JobListRequest{} 26 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 27 return nil, nil 28 } 29 30 var out structs.JobListResponse 31 if err := s.agent.RPC("Job.List", &args, &out); err != nil { 32 return nil, err 33 } 34 35 setMeta(resp, &out.QueryMeta) 36 if out.Jobs == nil { 37 out.Jobs = make([]*structs.JobListStub, 0) 38 } 39 return out.Jobs, nil 40 } 41 42 func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 43 path := strings.TrimPrefix(req.URL.Path, "/v1/job/") 44 switch { 45 case strings.HasSuffix(path, "/evaluate"): 46 jobName := strings.TrimSuffix(path, "/evaluate") 47 return s.jobForceEvaluate(resp, req, jobName) 48 case strings.HasSuffix(path, "/allocations"): 49 jobName := strings.TrimSuffix(path, "/allocations") 50 return s.jobAllocations(resp, req, jobName) 51 case strings.HasSuffix(path, "/evaluations"): 52 jobName := strings.TrimSuffix(path, "/evaluations") 53 return s.jobEvaluations(resp, req, jobName) 54 case strings.HasSuffix(path, "/periodic/force"): 55 jobName := strings.TrimSuffix(path, "/periodic/force") 56 return s.periodicForceRequest(resp, req, jobName) 57 case strings.HasSuffix(path, "/plan"): 58 jobName := strings.TrimSuffix(path, "/plan") 59 return s.jobPlan(resp, req, jobName) 60 case strings.HasSuffix(path, "/summary"): 61 jobName := strings.TrimSuffix(path, "/summary") 62 return s.jobSummaryRequest(resp, req, jobName) 63 case strings.HasSuffix(path, "/dispatch"): 64 jobName := strings.TrimSuffix(path, "/dispatch") 65 return s.jobDispatchRequest(resp, req, jobName) 66 default: 67 return s.jobCRUD(resp, req, path) 68 } 69 } 70 71 func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request, 72 jobName string) (interface{}, error) { 73 if req.Method != "PUT" && req.Method != "POST" { 74 return nil, CodedError(405, ErrInvalidMethod) 75 } 76 args := structs.JobEvaluateRequest{ 77 JobID: jobName, 78 } 79 s.parseRegion(req, &args.Region) 80 81 var out structs.JobRegisterResponse 82 if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil { 83 return nil, err 84 } 85 setIndex(resp, out.Index) 86 return out, nil 87 } 88 89 func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request, 90 jobName string) (interface{}, error) { 91 if req.Method != "PUT" && req.Method != "POST" { 92 return nil, CodedError(405, ErrInvalidMethod) 93 } 94 95 var args api.JobPlanRequest 96 if err := decodeBody(req, &args); err != nil { 97 return nil, CodedError(400, err.Error()) 98 } 99 if args.Job == nil { 100 return nil, CodedError(400, "Job must be specified") 101 } 102 if args.Job.ID == nil { 103 return nil, CodedError(400, "Job must have a valid ID") 104 } 105 if jobName != "" && *args.Job.ID != jobName { 106 return nil, CodedError(400, "Job ID does not match") 107 } 108 s.parseRegion(req, &args.Region) 109 110 sJob := ApiJobToStructJob(args.Job) 111 planReq := structs.JobPlanRequest{ 112 Job: sJob, 113 Diff: args.Diff, 114 WriteRequest: structs.WriteRequest{ 115 Region: args.WriteRequest.Region, 116 }, 117 } 118 var out structs.JobPlanResponse 119 if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil { 120 return nil, err 121 } 122 setIndex(resp, out.Index) 123 return out, nil 124 } 125 126 func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 127 // Ensure request method is POST or PUT 128 if !(req.Method == "POST" || req.Method == "PUT") { 129 return nil, CodedError(405, ErrInvalidMethod) 130 } 131 132 var validateRequest api.JobValidateRequest 133 if err := decodeBody(req, &validateRequest); err != nil { 134 return nil, CodedError(400, err.Error()) 135 } 136 if validateRequest.Job == nil { 137 return nil, CodedError(400, "Job must be specified") 138 } 139 140 job := ApiJobToStructJob(validateRequest.Job) 141 args := structs.JobValidateRequest{ 142 Job: job, 143 WriteRequest: structs.WriteRequest{ 144 Region: validateRequest.Region, 145 }, 146 } 147 s.parseRegion(req, &args.Region) 148 149 var out structs.JobValidateResponse 150 if err := s.agent.RPC("Job.Validate", &args, &out); err != nil { 151 return nil, err 152 } 153 154 return out, nil 155 } 156 157 func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request, 158 jobName string) (interface{}, error) { 159 if req.Method != "PUT" && req.Method != "POST" { 160 return nil, CodedError(405, ErrInvalidMethod) 161 } 162 163 args := structs.PeriodicForceRequest{ 164 JobID: jobName, 165 } 166 s.parseRegion(req, &args.Region) 167 168 var out structs.PeriodicForceResponse 169 if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil { 170 return nil, err 171 } 172 setIndex(resp, out.Index) 173 return out, nil 174 } 175 176 func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request, 177 jobName string) (interface{}, error) { 178 if req.Method != "GET" { 179 return nil, CodedError(405, ErrInvalidMethod) 180 } 181 allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all")) 182 183 args := structs.JobSpecificRequest{ 184 JobID: jobName, 185 AllAllocs: allAllocs, 186 } 187 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 188 return nil, nil 189 } 190 191 var out structs.JobAllocationsResponse 192 if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil { 193 return nil, err 194 } 195 196 setMeta(resp, &out.QueryMeta) 197 if out.Allocations == nil { 198 out.Allocations = make([]*structs.AllocListStub, 0) 199 } 200 return out.Allocations, nil 201 } 202 203 func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request, 204 jobName string) (interface{}, error) { 205 if req.Method != "GET" { 206 return nil, CodedError(405, ErrInvalidMethod) 207 } 208 args := structs.JobSpecificRequest{ 209 JobID: jobName, 210 } 211 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 212 return nil, nil 213 } 214 215 var out structs.JobEvaluationsResponse 216 if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil { 217 return nil, err 218 } 219 220 setMeta(resp, &out.QueryMeta) 221 if out.Evaluations == nil { 222 out.Evaluations = make([]*structs.Evaluation, 0) 223 } 224 return out.Evaluations, nil 225 } 226 227 func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request, 228 jobName string) (interface{}, error) { 229 switch req.Method { 230 case "GET": 231 return s.jobQuery(resp, req, jobName) 232 case "PUT", "POST": 233 return s.jobUpdate(resp, req, jobName) 234 case "DELETE": 235 return s.jobDelete(resp, req, jobName) 236 default: 237 return nil, CodedError(405, ErrInvalidMethod) 238 } 239 } 240 241 func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request, 242 jobName string) (interface{}, error) { 243 args := structs.JobSpecificRequest{ 244 JobID: jobName, 245 } 246 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 247 return nil, nil 248 } 249 250 var out structs.SingleJobResponse 251 if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil { 252 return nil, err 253 } 254 255 setMeta(resp, &out.QueryMeta) 256 if out.Job == nil { 257 return nil, CodedError(404, "job not found") 258 } 259 260 // Decode the payload if there is any 261 job := out.Job 262 if len(job.Payload) != 0 { 263 decoded, err := snappy.Decode(nil, out.Job.Payload) 264 if err != nil { 265 return nil, err 266 } 267 job = job.Copy() 268 job.Payload = decoded 269 } 270 271 return job, nil 272 } 273 274 func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request, 275 jobName string) (interface{}, error) { 276 var args api.JobRegisterRequest 277 if err := decodeBody(req, &args); err != nil { 278 return nil, CodedError(400, err.Error()) 279 } 280 if args.Job == nil { 281 return nil, CodedError(400, "Job must be specified") 282 } 283 284 if args.Job.ID == nil { 285 return nil, CodedError(400, "Job ID hasn't been provided") 286 } 287 if jobName != "" && *args.Job.ID != jobName { 288 return nil, CodedError(400, "Job ID does not match name") 289 } 290 s.parseRegion(req, &args.Region) 291 292 sJob := ApiJobToStructJob(args.Job) 293 294 regReq := structs.JobRegisterRequest{ 295 Job: sJob, 296 EnforceIndex: args.EnforceIndex, 297 JobModifyIndex: args.JobModifyIndex, 298 WriteRequest: structs.WriteRequest{ 299 Region: args.WriteRequest.Region, 300 }, 301 } 302 var out structs.JobRegisterResponse 303 if err := s.agent.RPC("Job.Register", ®Req, &out); err != nil { 304 return nil, err 305 } 306 setIndex(resp, out.Index) 307 return out, nil 308 } 309 310 func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request, 311 jobName string) (interface{}, error) { 312 args := structs.JobDeregisterRequest{ 313 JobID: jobName, 314 } 315 s.parseRegion(req, &args.Region) 316 317 var out structs.JobDeregisterResponse 318 if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil { 319 return nil, err 320 } 321 setIndex(resp, out.Index) 322 return out, nil 323 } 324 325 func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 326 args := structs.JobSummaryRequest{ 327 JobID: name, 328 } 329 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 330 return nil, nil 331 } 332 333 var out structs.JobSummaryResponse 334 if err := s.agent.RPC("Job.Summary", &args, &out); err != nil { 335 return nil, err 336 } 337 338 setMeta(resp, &out.QueryMeta) 339 if out.JobSummary == nil { 340 return nil, CodedError(404, "job not found") 341 } 342 setIndex(resp, out.Index) 343 return out.JobSummary, nil 344 } 345 346 func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 347 if req.Method != "PUT" && req.Method != "POST" { 348 return nil, CodedError(405, ErrInvalidMethod) 349 } 350 args := structs.JobDispatchRequest{} 351 if err := decodeBody(req, &args); err != nil { 352 return nil, CodedError(400, err.Error()) 353 } 354 if args.JobID != "" && args.JobID != name { 355 return nil, CodedError(400, "Job ID does not match") 356 } 357 if args.JobID == "" { 358 args.JobID = name 359 } 360 361 s.parseRegion(req, &args.Region) 362 363 var out structs.JobDispatchResponse 364 if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil { 365 return nil, err 366 } 367 setIndex(resp, out.Index) 368 return out, nil 369 } 370 371 func ApiJobToStructJob(job *api.Job) *structs.Job { 372 job.Canonicalize() 373 374 j := &structs.Job{ 375 Region: *job.Region, 376 ID: *job.ID, 377 ParentID: *job.ParentID, 378 Name: *job.Name, 379 Type: *job.Type, 380 Priority: *job.Priority, 381 AllAtOnce: *job.AllAtOnce, 382 Datacenters: job.Datacenters, 383 Payload: job.Payload, 384 Meta: job.Meta, 385 VaultToken: *job.VaultToken, 386 Status: *job.Status, 387 StatusDescription: *job.StatusDescription, 388 CreateIndex: *job.CreateIndex, 389 ModifyIndex: *job.ModifyIndex, 390 JobModifyIndex: *job.JobModifyIndex, 391 } 392 393 j.Constraints = make([]*structs.Constraint, len(job.Constraints)) 394 for i, c := range job.Constraints { 395 con := &structs.Constraint{} 396 ApiConstraintToStructs(c, con) 397 j.Constraints[i] = con 398 } 399 if job.Update != nil { 400 j.Update = structs.UpdateStrategy{ 401 Stagger: job.Update.Stagger, 402 MaxParallel: job.Update.MaxParallel, 403 } 404 } 405 if job.Periodic != nil { 406 j.Periodic = &structs.PeriodicConfig{ 407 Enabled: *job.Periodic.Enabled, 408 SpecType: *job.Periodic.SpecType, 409 ProhibitOverlap: *job.Periodic.ProhibitOverlap, 410 TimeZone: *job.Periodic.TimeZone, 411 } 412 if job.Periodic.Spec != nil { 413 j.Periodic.Spec = *job.Periodic.Spec 414 } 415 } 416 if job.ParameterizedJob != nil { 417 j.ParameterizedJob = &structs.ParameterizedJobConfig{ 418 Payload: job.ParameterizedJob.Payload, 419 MetaRequired: job.ParameterizedJob.MetaRequired, 420 MetaOptional: job.ParameterizedJob.MetaOptional, 421 } 422 } 423 424 j.TaskGroups = make([]*structs.TaskGroup, len(job.TaskGroups)) 425 for i, taskGroup := range job.TaskGroups { 426 tg := &structs.TaskGroup{} 427 ApiTgToStructsTG(taskGroup, tg) 428 j.TaskGroups[i] = tg 429 } 430 431 return j 432 } 433 434 func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) { 435 tg.Name = *taskGroup.Name 436 tg.Count = *taskGroup.Count 437 tg.Meta = taskGroup.Meta 438 tg.Constraints = make([]*structs.Constraint, len(taskGroup.Constraints)) 439 for k, constraint := range taskGroup.Constraints { 440 c := &structs.Constraint{} 441 ApiConstraintToStructs(constraint, c) 442 tg.Constraints[k] = c 443 } 444 tg.RestartPolicy = &structs.RestartPolicy{ 445 Attempts: *taskGroup.RestartPolicy.Attempts, 446 Interval: *taskGroup.RestartPolicy.Interval, 447 Delay: *taskGroup.RestartPolicy.Delay, 448 Mode: *taskGroup.RestartPolicy.Mode, 449 } 450 tg.EphemeralDisk = &structs.EphemeralDisk{ 451 Sticky: *taskGroup.EphemeralDisk.Sticky, 452 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 453 Migrate: *taskGroup.EphemeralDisk.Migrate, 454 } 455 tg.Meta = taskGroup.Meta 456 tg.Tasks = make([]*structs.Task, len(taskGroup.Tasks)) 457 for l, task := range taskGroup.Tasks { 458 t := &structs.Task{} 459 ApiTaskToStructsTask(task, t) 460 tg.Tasks[l] = t 461 } 462 } 463 464 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 465 structsTask.Name = apiTask.Name 466 structsTask.Driver = apiTask.Driver 467 structsTask.User = apiTask.User 468 structsTask.Leader = apiTask.Leader 469 structsTask.Config = apiTask.Config 470 structsTask.Constraints = make([]*structs.Constraint, len(apiTask.Constraints)) 471 for i, constraint := range apiTask.Constraints { 472 c := &structs.Constraint{} 473 ApiConstraintToStructs(constraint, c) 474 structsTask.Constraints[i] = c 475 } 476 structsTask.Env = apiTask.Env 477 structsTask.Services = make([]*structs.Service, len(apiTask.Services)) 478 for i, service := range apiTask.Services { 479 structsTask.Services[i] = &structs.Service{ 480 Name: service.Name, 481 PortLabel: service.PortLabel, 482 Tags: service.Tags, 483 } 484 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, len(service.Checks)) 485 for j, check := range service.Checks { 486 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 487 Name: check.Name, 488 Type: check.Type, 489 Command: check.Command, 490 Args: check.Args, 491 Path: check.Path, 492 Protocol: check.Protocol, 493 PortLabel: check.PortLabel, 494 Interval: check.Interval, 495 Timeout: check.Timeout, 496 InitialStatus: check.InitialStatus, 497 } 498 } 499 } 500 structsTask.Resources = &structs.Resources{ 501 CPU: *apiTask.Resources.CPU, 502 MemoryMB: *apiTask.Resources.MemoryMB, 503 IOPS: *apiTask.Resources.IOPS, 504 } 505 structsTask.Resources.Networks = make([]*structs.NetworkResource, len(apiTask.Resources.Networks)) 506 for i, nw := range apiTask.Resources.Networks { 507 structsTask.Resources.Networks[i] = &structs.NetworkResource{ 508 CIDR: nw.CIDR, 509 IP: nw.IP, 510 MBits: *nw.MBits, 511 } 512 structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, len(nw.DynamicPorts)) 513 structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, len(nw.ReservedPorts)) 514 for j, dp := range nw.DynamicPorts { 515 structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{ 516 Label: dp.Label, 517 Value: dp.Value, 518 } 519 } 520 for j, rp := range nw.ReservedPorts { 521 structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{ 522 Label: rp.Label, 523 Value: rp.Value, 524 } 525 } 526 } 527 structsTask.Meta = apiTask.Meta 528 structsTask.KillTimeout = *apiTask.KillTimeout 529 structsTask.LogConfig = &structs.LogConfig{ 530 MaxFiles: *apiTask.LogConfig.MaxFiles, 531 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 532 } 533 structsTask.Artifacts = make([]*structs.TaskArtifact, len(apiTask.Artifacts)) 534 for k, ta := range apiTask.Artifacts { 535 structsTask.Artifacts[k] = &structs.TaskArtifact{ 536 GetterSource: *ta.GetterSource, 537 GetterOptions: ta.GetterOptions, 538 RelativeDest: *ta.RelativeDest, 539 } 540 } 541 if apiTask.Vault != nil { 542 structsTask.Vault = &structs.Vault{ 543 Policies: apiTask.Vault.Policies, 544 Env: *apiTask.Vault.Env, 545 ChangeMode: *apiTask.Vault.ChangeMode, 546 ChangeSignal: *apiTask.Vault.ChangeSignal, 547 } 548 } 549 structsTask.Templates = make([]*structs.Template, len(apiTask.Templates)) 550 for i, template := range apiTask.Templates { 551 structsTask.Templates[i] = &structs.Template{ 552 SourcePath: *template.SourcePath, 553 DestPath: *template.DestPath, 554 EmbeddedTmpl: *template.EmbeddedTmpl, 555 ChangeMode: *template.ChangeMode, 556 ChangeSignal: *template.ChangeSignal, 557 Splay: *template.Splay, 558 Perms: *template.Perms, 559 LeftDelim: *template.LeftDelim, 560 RightDelim: *template.RightDelim, 561 } 562 } 563 if apiTask.DispatchPayload != nil { 564 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 565 File: apiTask.DispatchPayload.File, 566 } 567 } 568 } 569 570 func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) { 571 c2.LTarget = c1.LTarget 572 c2.RTarget = c1.RTarget 573 c2.Operand = c1.Operand 574 }