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", &regReq, &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  }