Move endpoint to be under job
This commit is contained in:
parent
a7b986328a
commit
5e900b94d0
15
api/jobs.go
15
api/jobs.go
|
@ -106,6 +106,21 @@ func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta,
|
|||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// PeriodicForce spawns a new instance of the periodic job and returns the eval ID
|
||||
func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp periodicForceResponse
|
||||
wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// periodicForceResponse is used to deserialize a force response
|
||||
type periodicForceResponse struct {
|
||||
EvalID string
|
||||
}
|
||||
|
||||
// UpdateStrategy is for serializing update strategy for a job.
|
||||
type UpdateStrategy struct {
|
||||
Stagger time.Duration
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
)
|
||||
|
||||
func TestJobs_Register(t *testing.T) {
|
||||
|
@ -295,6 +297,58 @@ func TestJobs_ForceEvaluate(t *testing.T) {
|
|||
t.Fatalf("evaluation %q missing", evalID)
|
||||
}
|
||||
|
||||
func TestJobs_PeriodicForce(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Force-eval on a non-existent job fails
|
||||
_, _, err := jobs.PeriodicForce("job1", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Create a new job
|
||||
job := testPeriodicJob()
|
||||
_, _, err = jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := jobs.Info(job.ID, nil)
|
||||
if err != nil || out == nil || out.ID != job.ID {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Try force again
|
||||
evalID, wm, err := jobs.PeriodicForce(job.ID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
if evalID == "" {
|
||||
t.Fatalf("empty evalID")
|
||||
}
|
||||
|
||||
// Retrieve the eval
|
||||
evals := c.Evaluations()
|
||||
eval, qm, err := evals.Info(evalID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
if eval.ID == evalID {
|
||||
return
|
||||
}
|
||||
t.Fatalf("evaluation %q missing", evalID)
|
||||
}
|
||||
|
||||
func TestJobs_NewBatchJob(t *testing.T) {
|
||||
job := NewBatchJob("job1", "myjob", "region1", 5)
|
||||
expect := &Job{
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package api
|
||||
|
||||
// Periodic is used to access periodic job endpoints
|
||||
type Periodic struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Periodic returns a handle to access periodic job endpoints.
|
||||
func (c *Client) PeriodicJobs() *Periodic {
|
||||
return &Periodic{client: c}
|
||||
}
|
||||
|
||||
// Force spawns a new instance of the periodic job and returns the eval ID
|
||||
func (p *Periodic) Force(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp periodicForceResponse
|
||||
wm, err := p.client.write("/v1/periodic/"+jobID+"/force", nil, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// periodicForceResponse is used to deserialize a force response
|
||||
type periodicForceResponse struct {
|
||||
EvalID string
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
)
|
||||
|
||||
func TestPeriodicForce(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
periodic := c.PeriodicJobs()
|
||||
|
||||
// Force-eval on a non-existent job fails
|
||||
_, _, err := periodic.Force("job1", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Create a new job
|
||||
job := testPeriodicJob()
|
||||
_, _, err = jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := jobs.Info(job.ID, nil)
|
||||
if err != nil || out == nil || out.ID != job.ID {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Try force again
|
||||
evalID, wm, err := periodic.Force(job.ID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
if evalID == "" {
|
||||
t.Fatalf("empty evalID")
|
||||
}
|
||||
|
||||
// Retrieve the eval
|
||||
evals := c.Evaluations()
|
||||
eval, qm, err := evals.Info(evalID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
if eval.ID == evalID {
|
||||
return
|
||||
}
|
||||
t.Fatalf("evaluation %q missing", evalID)
|
||||
}
|
|
@ -114,8 +114,6 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
|
|||
s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest))
|
||||
s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest))
|
||||
|
||||
s.mux.HandleFunc("/v1/periodic/", s.wrap(s.PeriodicSpecificReqeust))
|
||||
|
||||
if enableDebug {
|
||||
s.mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
|
|
|
@ -48,6 +48,9 @@ func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Requ
|
|||
case strings.HasSuffix(path, "/evaluations"):
|
||||
jobName := strings.TrimSuffix(path, "/evaluations")
|
||||
return s.jobEvaluations(resp, req, jobName)
|
||||
case strings.HasSuffix(path, "/periodic/force"):
|
||||
jobName := strings.TrimSuffix(path, "/periodic/force")
|
||||
return s.periodicForceRequest(resp, req, jobName)
|
||||
default:
|
||||
return s.jobCRUD(resp, req, path)
|
||||
}
|
||||
|
@ -71,6 +74,25 @@ func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Reques
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
|
||||
jobName string) (interface{}, error) {
|
||||
if req.Method != "PUT" && req.Method != "POST" {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
args := structs.PeriodicForceRequest{
|
||||
JobID: jobName,
|
||||
}
|
||||
s.parseRegion(req, &args.Region)
|
||||
|
||||
var out structs.PeriodicForceResponse
|
||||
if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request,
|
||||
jobName string) (interface{}, error) {
|
||||
if req.Method != "GET" {
|
||||
|
|
|
@ -443,3 +443,42 @@ func TestHTTP_JobAllocations(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_PeriodicForce(t *testing.T) {
|
||||
httpTest(t, nil, func(s *TestServer) {
|
||||
// Create and register a periodic job.
|
||||
job := mock.PeriodicJob()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"periodic/force", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.JobSpecificRequest(respW, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
|
||||
// Check the response
|
||||
r := obj.(structs.PeriodicForceResponse)
|
||||
if r.EvalID == "" {
|
||||
t.Fatalf("bad: %#v", r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
func (s *HTTPServer) PeriodicSpecificReqeust(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
path := strings.TrimPrefix(req.URL.Path, "/v1/periodic/")
|
||||
switch {
|
||||
case strings.HasSuffix(path, "/force"):
|
||||
jobName := strings.TrimSuffix(path, "/force")
|
||||
return s.periodicForceRequest(resp, req, jobName)
|
||||
default:
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
|
||||
jobName string) (interface{}, error) {
|
||||
if req.Method != "PUT" && req.Method != "POST" {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
args := structs.PeriodicForceRequest{
|
||||
JobID: jobName,
|
||||
}
|
||||
s.parseRegion(req, &args.Region)
|
||||
|
||||
var out structs.PeriodicForceResponse
|
||||
if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
return out, nil
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
func TestHTTP_PeriodicForce(t *testing.T) {
|
||||
httpTest(t, nil, func(s *TestServer) {
|
||||
// Create and register a periodic job.
|
||||
job := mock.PeriodicJob()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("POST", "/v1/periodic/"+job.ID+"/force", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
obj, err := s.Server.PeriodicSpecificReqeust(respW, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check for the index
|
||||
if respW.HeaderMap.Get("X-Nomad-Index") == "" {
|
||||
t.Fatalf("missing index")
|
||||
}
|
||||
|
||||
// Check the response
|
||||
r := obj.(structs.PeriodicForceResponse)
|
||||
if r.EvalID == "" {
|
||||
t.Fatalf("bad: %#v", r)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -313,6 +313,39 @@ region is used; another region can be specified using the `?region=` query param
|
|||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Forces a new instance of the periodic job. A new instance will be created
|
||||
even if it violates the job's
|
||||
[`prohibit_overlap`](/docs/jobspec/index.html#prohibit_overlap) settings. As
|
||||
such, this should be only used to immediately run a periodic job.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>PUT or POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/v1/job/<ID>/periodic/force`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"EvalCreateIndex": 7,
|
||||
"EvalID": "57983ddd-7fcf-3e3a-fd24-f699ccfb36f4"
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## DELETE
|
||||
|
||||
<dl>
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /v1/periodic"
|
||||
sidebar_current: "docs-http-periodic"
|
||||
description: >
|
||||
The '/v1/periodic' endpoint is used to interact with periodic jobs.
|
||||
---
|
||||
|
||||
# /v1/periodic
|
||||
|
||||
The `periodic` endpoint is used to interact with a single periodic job. By
|
||||
default, the agent's local region is used; another region can be specified using
|
||||
the `?region=` query parameter.
|
||||
|
||||
## PUT / POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Forces a new instance of the periodic job. A new instance will be created
|
||||
even if it violates the job's
|
||||
[`prohibit_overlap`](/docs/jobspec/index.html#prohibit_overlap) settings. As
|
||||
such, this should be only used to immediately run a periodic job.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>PUT or POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/v1/periodic/<ID>/force`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"EvalCreateIndex": 7,
|
||||
"EvalID": "57983ddd-7fcf-3e3a-fd24-f699ccfb36f4"
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -97,10 +97,6 @@
|
|||
<a href="/docs/http/status.html">Status</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-periodic") %>>
|
||||
<a href="/docs/http/periodic.html">Periodic Jobs</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
Loading…
Reference in New Issue