commit
3ce09d5c34
157
api/agent.go
Normal file
157
api/agent.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Agent encapsulates an API client which talks to Nomad's
|
||||
// agent endpoints for a specific node.
|
||||
type Agent struct {
|
||||
client *Client
|
||||
|
||||
// Cache static agent info
|
||||
nodeName string
|
||||
datacenter string
|
||||
region string
|
||||
}
|
||||
|
||||
// Agent returns a new agent which can be used to query
|
||||
// the agent-specific endpoints.
|
||||
func (c *Client) Agent() *Agent {
|
||||
return &Agent{client: c}
|
||||
}
|
||||
|
||||
// Self is used to query the /v1/agent/self endpoint and
|
||||
// returns information specific to the running agent.
|
||||
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
||||
var out map[string]map[string]interface{}
|
||||
|
||||
// Query the self endpoint on the agent
|
||||
_, err := a.client.query("/v1/agent/self", &out, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed querying self endpoint: %s", err)
|
||||
}
|
||||
|
||||
// Populate the cache for faster queries
|
||||
a.populateCache(out)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// populateCache is used to insert various pieces of static
|
||||
// data into the agent handle. This is used during subsequent
|
||||
// lookups for the same data later on to save the round trip.
|
||||
func (a *Agent) populateCache(info map[string]map[string]interface{}) {
|
||||
if a.nodeName == "" {
|
||||
a.nodeName, _ = info["member"]["Name"].(string)
|
||||
}
|
||||
if tags, ok := info["member"]["Tags"].(map[string]interface{}); ok {
|
||||
if a.datacenter == "" {
|
||||
a.datacenter, _ = tags["dc"].(string)
|
||||
}
|
||||
if a.region == "" {
|
||||
a.region, _ = tags["region"].(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NodeName is used to query the Nomad agent for its node name.
|
||||
func (a *Agent) NodeName() (string, error) {
|
||||
// Return from cache if we have it
|
||||
if a.nodeName != "" {
|
||||
return a.nodeName, nil
|
||||
}
|
||||
|
||||
// Query the node name
|
||||
_, err := a.Self()
|
||||
return a.nodeName, err
|
||||
}
|
||||
|
||||
// Datacenter is used to return the name of the datacenter which
|
||||
// the agent is a member of.
|
||||
func (a *Agent) Datacenter() (string, error) {
|
||||
// Return from cache if we have it
|
||||
if a.datacenter != "" {
|
||||
return a.datacenter, nil
|
||||
}
|
||||
|
||||
// Query the agent for the DC
|
||||
_, err := a.Self()
|
||||
return a.datacenter, err
|
||||
}
|
||||
|
||||
// Region is used to look up the region the agent is in.
|
||||
func (a *Agent) Region() (string, error) {
|
||||
// Return from cache if we have it
|
||||
if a.region != "" {
|
||||
return a.region, nil
|
||||
}
|
||||
|
||||
// Query the agent for the region
|
||||
_, err := a.Self()
|
||||
return a.region, err
|
||||
}
|
||||
|
||||
// Join is used to instruct a server node to join another server
|
||||
// via the gossip protocol. Multiple addresses may be specified.
|
||||
// We attempt to join all of the hosts in the list. If one or
|
||||
// more nodes have a successful result, no error is returned.
|
||||
func (a *Agent) Join(addrs ...string) error {
|
||||
// Accumulate the addresses
|
||||
v := url.Values{}
|
||||
for _, addr := range addrs {
|
||||
v.Add("address", addr)
|
||||
}
|
||||
|
||||
// Send the join request
|
||||
var resp joinResponse
|
||||
_, err := a.client.write("/v1/agent/join?"+v.Encode(), nil, &resp, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed joining: %s", err)
|
||||
}
|
||||
if resp.Error != "" {
|
||||
return fmt.Errorf("failed joining: %s", resp.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Members is used to query all of the known server members
|
||||
func (a *Agent) Members() ([]*AgentMember, error) {
|
||||
var resp []*AgentMember
|
||||
|
||||
// Query the known members
|
||||
_, err := a.client.query("/v1/agent/members", &resp, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ForceLeave is used to eject an existing node from the cluster.
|
||||
func (a *Agent) ForceLeave(node string) error {
|
||||
_, err := a.client.write("/v1/agent/force-leave?node="+node, nil, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// joinResponse is used to decode the response we get while
|
||||
// sending a member join request.
|
||||
type joinResponse struct {
|
||||
NumNodes int `json:"num_nodes"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// AgentMember represents a cluster member known to the agent
|
||||
type AgentMember struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port uint16
|
||||
Tags map[string]string
|
||||
Status int
|
||||
ProtocolMin uint8
|
||||
ProtocolMax uint8
|
||||
ProtocolCur uint8
|
||||
DelegateMin uint8
|
||||
DelegateMax uint8
|
||||
DelegateCur uint8
|
||||
}
|
111
api/agent_test.go
Normal file
111
api/agent_test.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAgent_Self(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
// Get a handle on the Agent endpoints
|
||||
a := c.Agent()
|
||||
|
||||
// Query the endpoint
|
||||
res, err := a.Self()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Check that we got a valid response
|
||||
if name, ok := res["member"]["Name"]; !ok || name == "" {
|
||||
t.Fatalf("bad member name in response: %#v", res)
|
||||
}
|
||||
|
||||
// Local cache was populated
|
||||
if a.nodeName == "" || a.datacenter == "" || a.region == "" {
|
||||
t.Fatalf("cache should be populated, got: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_NodeName(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Agent()
|
||||
|
||||
// Query the agent for the node name
|
||||
res, err := a.NodeName()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if res == "" {
|
||||
t.Fatalf("expected node name, got nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Datacenter(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Agent()
|
||||
|
||||
// Query the agent for the datacenter
|
||||
dc, err := a.Datacenter()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if dc != "dc1" {
|
||||
t.Fatalf("expected dc1, got: %q", dc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Join(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Agent()
|
||||
|
||||
// Attempting to join a non-existent host returns error
|
||||
if err := a.Join("nope"); err == nil {
|
||||
t.Fatalf("expected error, got nothing")
|
||||
}
|
||||
|
||||
// TODO(ryanuber): This is pretty much a worthless test,
|
||||
// since we are just joining ourselves. Once the agent
|
||||
// respects config options, change this to actually make
|
||||
// two nodes and join them.
|
||||
if err := a.Join("127.0.0.1"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Members(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Agent()
|
||||
|
||||
// Query nomad for all the known members
|
||||
mem, err := a.Members()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Check that we got the expected result
|
||||
if n := len(mem); n != 1 {
|
||||
t.Fatalf("expected 1 member, got: %d", n)
|
||||
}
|
||||
if m := mem[0]; m.Name == "" || m.Addr == "" || m.Port == 0 {
|
||||
t.Fatalf("bad member: %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_ForceLeave(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Agent()
|
||||
|
||||
// Force-leave on a non-existent node does not error
|
||||
if err := a.ForceLeave("nope"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// TODO: test force-leave on an existing node
|
||||
}
|
45
api/allocations.go
Normal file
45
api/allocations.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
// Allocations is used to query the alloc-related endpoints.
|
||||
type Allocations struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Allocations returns a handle on the allocs endpoints.
|
||||
func (c *Client) Allocations() *Allocations {
|
||||
return &Allocations{client: c}
|
||||
}
|
||||
|
||||
// List returns a list of all of the allocations.
|
||||
func (a *Allocations) List(q *QueryOptions) ([]*Allocation, *QueryMeta, error) {
|
||||
var resp []*Allocation
|
||||
qm, err := a.client.query("/v1/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Info is used to retrieve a single allocation.
|
||||
func (a *Allocations) Info(allocID string, q *QueryOptions) (*Allocation, *QueryMeta, error) {
|
||||
var resp Allocation
|
||||
qm, err := a.client.query("/v1/allocation/"+allocID, &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// Allocation is used for serialization of allocations.
|
||||
type Allocation struct {
|
||||
ID string
|
||||
EvalID string
|
||||
Name string
|
||||
NodeID string
|
||||
JobID string
|
||||
TaskGroup string
|
||||
DesiredStatus string
|
||||
DesiredDescription string
|
||||
ClientStatus string
|
||||
ClientDescription string
|
||||
}
|
51
api/allocations_test.go
Normal file
51
api/allocations_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllocations_List(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
a := c.Allocations()
|
||||
|
||||
// Querying when no allocs exist returns nothing
|
||||
allocs, qm, err := a.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(allocs); n != 0 {
|
||||
t.Fatalf("expected 0 allocs, got: %d", n)
|
||||
}
|
||||
|
||||
// TODO: do something that causes an allocation to actually happen
|
||||
// so we can query for them.
|
||||
return
|
||||
|
||||
job := &Job{
|
||||
ID: "job1",
|
||||
Name: "Job #1",
|
||||
Type: JobTypeService,
|
||||
}
|
||||
eval, _, err := c.Jobs().Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// List the allocations again
|
||||
allocs, qm, err = a.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
|
||||
// Check that we got the allocation back
|
||||
if len(allocs) == 0 || allocs[0].EvalID != eval {
|
||||
t.Fatalf("bad: %#v", allocs)
|
||||
}
|
||||
}
|
11
api/api.go
11
api/api.go
|
@ -195,13 +195,14 @@ func (r *request) toHTTP() (*http.Request, error) {
|
|||
// newRequest is used to create a new request
|
||||
func (c *Client) newRequest(method, path string) *request {
|
||||
base, _ := url.Parse(c.config.URL)
|
||||
u, _ := url.Parse(path)
|
||||
r := &request{
|
||||
config: &c.config,
|
||||
method: method,
|
||||
url: &url.URL{
|
||||
Scheme: base.Scheme,
|
||||
Host: base.Host,
|
||||
Path: path,
|
||||
Path: u.Path,
|
||||
},
|
||||
params: make(map[string][]string),
|
||||
}
|
||||
|
@ -211,6 +212,14 @@ func (c *Client) newRequest(method, path string) *request {
|
|||
if c.config.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
||||
}
|
||||
|
||||
// Add in the query parameters, if any
|
||||
for key, values := range u.Query() {
|
||||
for _, value := range values {
|
||||
r.params.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ func makeClient(t *testing.T, cb1 configCallback,
|
|||
|
||||
// Create server
|
||||
server := testutil.NewTestServer(t, cb2)
|
||||
conf.URL = server.HTTPAddr
|
||||
conf.URL = "http://" + server.HTTPAddr
|
||||
|
||||
// Create client
|
||||
client, err := NewClient(conf)
|
||||
|
@ -155,3 +155,22 @@ func TestParseWriteMeta(t *testing.T) {
|
|||
t.Fatalf("Bad: %v", wm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryString(t *testing.T) {
|
||||
// TODO t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
r := c.newRequest("PUT", "/v1/abc?foo=bar&baz=zip")
|
||||
q := &WriteOptions{Region: "foo"}
|
||||
r.setWriteOptions(q)
|
||||
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if uri := req.URL.RequestURI(); uri != "/v1/abc?baz=zip&foo=bar®ion=foo" {
|
||||
t.Fatalf("bad uri: %q", uri)
|
||||
}
|
||||
}
|
||||
|
|
121
api/compose_test.go
Normal file
121
api/compose_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
// Compose a task
|
||||
task := NewTask("task1", "exec").
|
||||
SetConfig("foo", "bar").
|
||||
SetMeta("foo", "bar").
|
||||
Constrain(HardConstraint("kernel.name", "=", "linux")).
|
||||
Require(&Resources{
|
||||
CPU: 1.25,
|
||||
MemoryMB: 1024,
|
||||
DiskMB: 2048,
|
||||
IOPS: 1024,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{80, 443},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Compose a task group
|
||||
grp := NewTaskGroup("grp1", 2).
|
||||
Constrain(HardConstraint("kernel.name", "=", "linux")).
|
||||
SetMeta("foo", "bar").
|
||||
AddTask(task)
|
||||
|
||||
// Compose a job
|
||||
job := NewServiceJob("job1", "myjob", 2).
|
||||
SetMeta("foo", "bar").
|
||||
AddDatacenter("dc1").
|
||||
Constrain(HardConstraint("kernel.name", "=", "linux")).
|
||||
AddTaskGroup(grp)
|
||||
|
||||
// Check that the composed result looks correct
|
||||
expect := &Job{
|
||||
ID: "job1",
|
||||
Name: "myjob",
|
||||
Type: JobTypeService,
|
||||
Priority: 2,
|
||||
Datacenters: []string{
|
||||
"dc1",
|
||||
},
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Constraints: []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "linux",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
TaskGroups: []*TaskGroup{
|
||||
&TaskGroup{
|
||||
Name: "grp1",
|
||||
Count: 2,
|
||||
Constraints: []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "linux",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
Tasks: []*Task{
|
||||
&Task{
|
||||
Name: "task1",
|
||||
Driver: "exec",
|
||||
Resources: &Resources{
|
||||
CPU: 1.25,
|
||||
MemoryMB: 1024,
|
||||
DiskMB: 2048,
|
||||
IOPS: 1024,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{
|
||||
80,
|
||||
443,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Constraints: []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "linux",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
Config: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Meta: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(job, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, job)
|
||||
}
|
||||
}
|
33
api/constraint.go
Normal file
33
api/constraint.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package api
|
||||
|
||||
// Constraint is used to serialize a job placement constraint.
|
||||
type Constraint struct {
|
||||
Hard bool
|
||||
LTarget string
|
||||
RTarget string
|
||||
Operand string
|
||||
Weight int
|
||||
}
|
||||
|
||||
// HardConstraint is used to create a new hard constraint.
|
||||
func HardConstraint(left, operand, right string) *Constraint {
|
||||
return constraint(left, operand, right, true, 0)
|
||||
}
|
||||
|
||||
// SoftConstraint is used to create a new soft constraint. It
|
||||
// takes an additional weight parameter to allow balancing
|
||||
// multiple soft constraints amongst eachother.
|
||||
func SoftConstraint(left, operand, right string, weight int) *Constraint {
|
||||
return constraint(left, operand, right, false, weight)
|
||||
}
|
||||
|
||||
// constraint generates a new job placement constraint.
|
||||
func constraint(left, operand, right string, hard bool, weight int) *Constraint {
|
||||
return &Constraint{
|
||||
Hard: hard,
|
||||
LTarget: left,
|
||||
RTarget: right,
|
||||
Operand: operand,
|
||||
Weight: weight,
|
||||
}
|
||||
}
|
32
api/constraint_test.go
Normal file
32
api/constraint_test.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompose_Constraints(t *testing.T) {
|
||||
c := HardConstraint("kernel.name", "=", "darwin")
|
||||
expect := &Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "darwin",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
}
|
||||
if !reflect.DeepEqual(c, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, c)
|
||||
}
|
||||
|
||||
c = SoftConstraint("memory.totalbytes", ">=", "250000000", 5)
|
||||
expect = &Constraint{
|
||||
Hard: false,
|
||||
LTarget: "memory.totalbytes",
|
||||
RTarget: "250000000",
|
||||
Operand: ">=",
|
||||
Weight: 5,
|
||||
}
|
||||
if !reflect.DeepEqual(c, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, c)
|
||||
}
|
||||
}
|
63
api/evaluations.go
Normal file
63
api/evaluations.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Evaluations is used to query the evaluation endpoints.
|
||||
type Evaluations struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Evaluations returns a new handle on the evaluations.
|
||||
func (c *Client) Evaluations() *Evaluations {
|
||||
return &Evaluations{client: c}
|
||||
}
|
||||
|
||||
// List is used to dump all of the evaluations.
|
||||
func (e *Evaluations) List(q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
|
||||
var resp []*Evaluation
|
||||
qm, err := e.client.query("/v1/evaluations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Info is used to query a single evaluation by its ID.
|
||||
func (e *Evaluations) Info(evalID string, q *QueryOptions) (*Evaluation, *QueryMeta, error) {
|
||||
var resp Evaluation
|
||||
qm, err := e.client.query("/v1/evaluation/"+evalID, &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// Allocations is used to retrieve a set of allocations given
|
||||
// an evaluation ID.
|
||||
func (e *Evaluations) Allocations(evalID string, q *QueryOptions) ([]*Allocation, *QueryMeta, error) {
|
||||
var resp []*Allocation
|
||||
qm, err := e.client.query("/v1/evaluation/"+evalID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Evaluation is used to serialize an evaluation.
|
||||
type Evaluation struct {
|
||||
ID string
|
||||
Priority int
|
||||
Type string
|
||||
TriggeredBy string
|
||||
JobID string
|
||||
JobModifyIndex uint64
|
||||
NodeID string
|
||||
NodeModifyIndex uint64
|
||||
Status string
|
||||
StatusDescription string
|
||||
Wait time.Duration
|
||||
NextEval string
|
||||
PreviousEval string
|
||||
}
|
96
api/evaluations_test.go
Normal file
96
api/evaluations_test.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvaluations_List(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
e := c.Evaluations()
|
||||
|
||||
// Listing when nothing exists returns empty
|
||||
result, qm, err := e.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(result); n != 0 {
|
||||
t.Fatalf("expected 0 evaluations, got: %d", n)
|
||||
}
|
||||
|
||||
// Register a job. This will create an evaluation.
|
||||
jobs := c.Jobs()
|
||||
job := testJob()
|
||||
evalID, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Check the evaluations again
|
||||
result, qm, err = e.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check if we have the right list
|
||||
if len(result) != 1 || result[0].ID != evalID {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluations_Info(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
e := c.Evaluations()
|
||||
|
||||
// Querying a non-existent evaluation returns error
|
||||
_, _, err := e.Info("8E231CF4-CA48-43FF-B694-5801E69E22FA", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %s", err)
|
||||
}
|
||||
|
||||
// Register a job. Creates a new evaluation.
|
||||
jobs := c.Jobs()
|
||||
job := testJob()
|
||||
evalID, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Try looking up by the new eval ID
|
||||
result, qm, err := e.Info(evalID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that we got the right result
|
||||
if result == nil || result.ID != evalID {
|
||||
t.Fatalf("expected eval %q, got: %#v", evalID, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluations_Allocations(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
e := c.Evaluations()
|
||||
|
||||
// Returns empty if no allocations
|
||||
allocs, qm, err := e.Allocations("8E231CF4-CA48-43FF-B694-5801E69E22FA", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(allocs); n != 0 {
|
||||
t.Fatalf("expected 0 allocs, got: %d", n)
|
||||
}
|
||||
}
|
169
api/jobs.go
Normal file
169
api/jobs.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package api
|
||||
|
||||
const (
|
||||
// JobTypeService indicates a long-running processes
|
||||
JobTypeService = "service"
|
||||
|
||||
// JobTypeBatch indicates a short-lived process
|
||||
JobTypeBatch = "batch"
|
||||
)
|
||||
|
||||
// Jobs is used to access the job-specific endpoints.
|
||||
type Jobs struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Jobs returns a handle on the jobs endpoints.
|
||||
func (c *Client) Jobs() *Jobs {
|
||||
return &Jobs{client: c}
|
||||
}
|
||||
|
||||
// Register is used to register a new job. It returns the ID
|
||||
// of the evaluation, along with any errors encountered.
|
||||
func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp registerJobResponse
|
||||
|
||||
req := ®isterJobRequest{job}
|
||||
wm, err := j.client.write("/v1/jobs", req, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// List is used to list all of the existing jobs.
|
||||
func (j *Jobs) List(q *QueryOptions) ([]*Job, *QueryMeta, error) {
|
||||
var resp []*Job
|
||||
qm, err := j.client.query("/v1/jobs", &resp, q)
|
||||
if err != nil {
|
||||
return nil, qm, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Info is used to retrieve information about a particular
|
||||
// job given its unique ID.
|
||||
func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
|
||||
var resp Job
|
||||
qm, err := j.client.query("/v1/job/"+jobID, &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// Allocations is used to return the allocs for a given job ID.
|
||||
func (j *Jobs) Allocations(jobID string, q *QueryOptions) ([]*Allocation, *QueryMeta, error) {
|
||||
var resp []*Allocation
|
||||
qm, err := j.client.query("/v1/job/"+jobID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Evaluations is used to query the evaluations associated with
|
||||
// the given job ID.
|
||||
func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
|
||||
var resp []*Evaluation
|
||||
qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Delete is used to remove an existing job.
|
||||
func (j *Jobs) Delete(jobID string, q *WriteOptions) (*WriteMeta, error) {
|
||||
wm, err := j.client.delete("/v1/job/"+jobID, nil, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// ForceEvaluate is used to force-evaluate an existing job.
|
||||
func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp registerJobResponse
|
||||
wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// Job is used to serialize a job.
|
||||
type Job struct {
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Priority int
|
||||
AllAtOnce bool
|
||||
Datacenters []string
|
||||
Constraints []*Constraint
|
||||
TaskGroups []*TaskGroup
|
||||
Meta map[string]string
|
||||
Status string
|
||||
StatusDescription string
|
||||
}
|
||||
|
||||
// NewServiceJob creates and returns a new service-style job
|
||||
// for long-lived processes using the provided name, ID, and
|
||||
// relative job priority.
|
||||
func NewServiceJob(id, name string, pri int) *Job {
|
||||
return newJob(id, name, JobTypeService, pri)
|
||||
}
|
||||
|
||||
// NewBatchJob creates and returns a new batch-style job for
|
||||
// short-lived processes using the provided name and ID along
|
||||
// with the relative job priority.
|
||||
func NewBatchJob(id, name string, pri int) *Job {
|
||||
return newJob(id, name, JobTypeBatch, pri)
|
||||
}
|
||||
|
||||
// newJob is used to create a new Job struct.
|
||||
func newJob(jobID, jobName, jobType string, pri int) *Job {
|
||||
return &Job{
|
||||
ID: jobID,
|
||||
Name: jobName,
|
||||
Type: jobType,
|
||||
Priority: pri,
|
||||
}
|
||||
}
|
||||
|
||||
// SetMeta is used to set arbitrary k/v pairs of metadata on a job.
|
||||
func (j *Job) SetMeta(key, val string) *Job {
|
||||
if j.Meta == nil {
|
||||
j.Meta = make(map[string]string)
|
||||
}
|
||||
j.Meta[key] = val
|
||||
return j
|
||||
}
|
||||
|
||||
// AddDatacenter is used to add a datacenter to a job.
|
||||
func (j *Job) AddDatacenter(dc string) *Job {
|
||||
j.Datacenters = append(j.Datacenters, dc)
|
||||
return j
|
||||
}
|
||||
|
||||
// Constrain is used to add a constraint to a job.
|
||||
func (j *Job) Constrain(c *Constraint) *Job {
|
||||
j.Constraints = append(j.Constraints, c)
|
||||
return j
|
||||
}
|
||||
|
||||
// AddTaskGroup adds a task group to an existing job.
|
||||
func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
|
||||
j.TaskGroups = append(j.TaskGroups, grp)
|
||||
return j
|
||||
}
|
||||
|
||||
// registerJobRequest is used to serialize a job registration
|
||||
type registerJobRequest struct {
|
||||
Job *Job
|
||||
}
|
||||
|
||||
// registerJobResponse is used to deserialize a job response
|
||||
type registerJobResponse struct {
|
||||
EvalID string
|
||||
}
|
304
api/jobs_test.go
Normal file
304
api/jobs_test.go
Normal file
|
@ -0,0 +1,304 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJobs_Register(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Listing jobs before registering returns nothing
|
||||
resp, qm, err := jobs.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(resp); n != 0 {
|
||||
t.Fatalf("expected 0 jobs, got: %d", n)
|
||||
}
|
||||
|
||||
// Create a job and attempt to register it
|
||||
job := testJob()
|
||||
eval, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if eval == "" {
|
||||
t.Fatalf("missing eval id")
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Query the jobs back out again
|
||||
resp, qm, err = jobs.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that we got the expected response
|
||||
expect := []*Job{job}
|
||||
if !reflect.DeepEqual(resp, expect) {
|
||||
t.Fatalf("bad: %#v", resp[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_Info(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Trying to retrieve a job by ID before it exists
|
||||
// returns an error
|
||||
_, _, err := jobs.Info("job1", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Register the job
|
||||
job := testJob()
|
||||
_, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Query the job again and ensure it exists
|
||||
result, qm, err := jobs.Info("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that the result is what we expect
|
||||
if !reflect.DeepEqual(result, job) {
|
||||
t.Fatalf("expect: %#v, got: %#v", job, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_Allocations(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Looking up by a non-existent job returns nothing
|
||||
allocs, qm, err := jobs.Allocations("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(allocs); n != 0 {
|
||||
t.Fatalf("expected 0 allocs, got: %d", n)
|
||||
}
|
||||
|
||||
// TODO: do something here to create some allocations for
|
||||
// an existing job, lookup again.
|
||||
}
|
||||
|
||||
func TestJobs_Evaluations(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Looking up by a non-existent job ID returns nothing
|
||||
evals, qm, err := jobs.Evaluations("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if qm.LastIndex != 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if n := len(evals); n != 0 {
|
||||
t.Fatalf("expected 0 evals, got: %d", n)
|
||||
}
|
||||
|
||||
// Insert a job. This also creates an evaluation so we should
|
||||
// be able to query that out after.
|
||||
job := testJob()
|
||||
evalID, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Look up the evaluations again.
|
||||
evals, qm, err = jobs.Evaluations("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that we got the evals back
|
||||
if n := len(evals); n == 0 || evals[0].ID != evalID {
|
||||
t.Fatalf("expected 1 eval (%s), got: %#v", evalID, evals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_Delete(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
jobs := c.Jobs()
|
||||
|
||||
// Register a new job
|
||||
job := testJob()
|
||||
_, wm, err := jobs.Register(job, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Attempting delete on non-existing job does not error
|
||||
wm2, err := jobs.Delete("nope", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm2)
|
||||
|
||||
// Deleting an existing job works
|
||||
wm3, err := jobs.Delete("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm3)
|
||||
|
||||
// Check that the job is really gone
|
||||
result, qm, err := jobs.List(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
if n := len(result); n != 0 {
|
||||
t.Fatalf("expected 0 jobs, got: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_ForceEvaluate(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.ForceEvaluate("job1", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Create a new job
|
||||
_, wm, err := jobs.Register(testJob(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Try force-eval again
|
||||
evalID, wm, err := jobs.ForceEvaluate("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Retrieve the evals and see if we get a matching one
|
||||
evals, qm, err := jobs.Evaluations("job1", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
for _, eval := range evals {
|
||||
if eval.ID == evalID {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("evaluation %q missing", evalID)
|
||||
}
|
||||
|
||||
func TestJobs_NewBatchJob(t *testing.T) {
|
||||
job := NewBatchJob("job1", "myjob", 5)
|
||||
expect := &Job{
|
||||
ID: "job1",
|
||||
Name: "myjob",
|
||||
Type: JobTypeBatch,
|
||||
Priority: 5,
|
||||
}
|
||||
if !reflect.DeepEqual(job, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, job)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_NewServiceJob(t *testing.T) {
|
||||
job := NewServiceJob("job1", "myjob", 5)
|
||||
expect := &Job{
|
||||
ID: "job1",
|
||||
Name: "myjob",
|
||||
Type: JobTypeService,
|
||||
Priority: 5,
|
||||
}
|
||||
if !reflect.DeepEqual(job, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, job)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_SetMeta(t *testing.T) {
|
||||
job := &Job{Meta: nil}
|
||||
|
||||
// Initializes a nil map
|
||||
out := job.SetMeta("foo", "bar")
|
||||
if job.Meta == nil {
|
||||
t.Fatalf("should initialize metadata")
|
||||
}
|
||||
|
||||
// Check that the job was returned
|
||||
if job != out {
|
||||
t.Fatalf("expect: %#v, got: %#v", job, out)
|
||||
}
|
||||
|
||||
// Setting another pair is additive
|
||||
job.SetMeta("baz", "zip")
|
||||
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
||||
if !reflect.DeepEqual(job.Meta, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, job.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobs_Constrain(t *testing.T) {
|
||||
job := &Job{Constraints: nil}
|
||||
|
||||
// Create and add a constraint
|
||||
out := job.Constrain(HardConstraint("kernel.name", "=", "darwin"))
|
||||
if n := len(job.Constraints); n != 1 {
|
||||
t.Fatalf("expected 1 constraint, got: %d", n)
|
||||
}
|
||||
|
||||
// Check that the job was returned
|
||||
if job != out {
|
||||
t.Fatalf("expect: %#v, got: %#v", job, out)
|
||||
}
|
||||
|
||||
// Adding another constraint preserves the original
|
||||
job.Constrain(SoftConstraint("memory.totalbytes", ">=", "128000000", 2))
|
||||
expect := []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "darwin",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
&Constraint{
|
||||
Hard: false,
|
||||
LTarget: "memory.totalbytes",
|
||||
RTarget: "128000000",
|
||||
Operand: ">=",
|
||||
Weight: 2,
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(job.Constraints, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, job.Constraints)
|
||||
}
|
||||
}
|
20
api/resources.go
Normal file
20
api/resources.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package api
|
||||
|
||||
// Resources encapsulates the required resources of
|
||||
// a given task or task group.
|
||||
type Resources struct {
|
||||
CPU float64
|
||||
MemoryMB int
|
||||
DiskMB int
|
||||
IOPS int
|
||||
Networks []*NetworkResource
|
||||
}
|
||||
|
||||
// NetworkResource is used to describe required network
|
||||
// resources of a given task.
|
||||
type NetworkResource struct {
|
||||
Public bool
|
||||
CIDR string
|
||||
ReservedPorts []int
|
||||
MBits int
|
||||
}
|
88
api/tasks.go
Normal file
88
api/tasks.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package api
|
||||
|
||||
// TaskGroup is the unit of scheduling.
|
||||
type TaskGroup struct {
|
||||
Name string
|
||||
Count int
|
||||
Constraints []*Constraint
|
||||
Tasks []*Task
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
// NewTaskGroup creates a new TaskGroup.
|
||||
func NewTaskGroup(name string, count int) *TaskGroup {
|
||||
return &TaskGroup{
|
||||
Name: name,
|
||||
Count: count,
|
||||
}
|
||||
}
|
||||
|
||||
// Constrain is used to add a constraint to a task group.
|
||||
func (g *TaskGroup) Constrain(c *Constraint) *TaskGroup {
|
||||
g.Constraints = append(g.Constraints, c)
|
||||
return g
|
||||
}
|
||||
|
||||
// AddMeta is used to add a meta k/v pair to a task group
|
||||
func (g *TaskGroup) SetMeta(key, val string) *TaskGroup {
|
||||
if g.Meta == nil {
|
||||
g.Meta = make(map[string]string)
|
||||
}
|
||||
g.Meta[key] = val
|
||||
return g
|
||||
}
|
||||
|
||||
// AddTask is used to add a new task to a task group.
|
||||
func (g *TaskGroup) AddTask(t *Task) *TaskGroup {
|
||||
g.Tasks = append(g.Tasks, t)
|
||||
return g
|
||||
}
|
||||
|
||||
// Task is a single process in a task group.
|
||||
type Task struct {
|
||||
Name string
|
||||
Driver string
|
||||
Config map[string]string
|
||||
Constraints []*Constraint
|
||||
Resources *Resources
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
// NewTask creates and initializes a new Task.
|
||||
func NewTask(name, driver string) *Task {
|
||||
return &Task{
|
||||
Name: name,
|
||||
Driver: driver,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure is used to configure a single k/v pair on
|
||||
// the task.
|
||||
func (t *Task) SetConfig(key, val string) *Task {
|
||||
if t.Config == nil {
|
||||
t.Config = make(map[string]string)
|
||||
}
|
||||
t.Config[key] = val
|
||||
return t
|
||||
}
|
||||
|
||||
// SetMeta is used to add metadata k/v pairs to the task.
|
||||
func (t *Task) SetMeta(key, val string) *Task {
|
||||
if t.Meta == nil {
|
||||
t.Meta = make(map[string]string)
|
||||
}
|
||||
t.Meta[key] = val
|
||||
return t
|
||||
}
|
||||
|
||||
// Require is used to add resource requirements to a task.
|
||||
func (t *Task) Require(r *Resources) *Task {
|
||||
t.Resources = r
|
||||
return t
|
||||
}
|
||||
|
||||
// Constraint adds a new constraints to a single task.
|
||||
func (t *Task) Constrain(c *Constraint) *Task {
|
||||
t.Constraints = append(t.Constraints, c)
|
||||
return t
|
||||
}
|
227
api/tasks_test.go
Normal file
227
api/tasks_test.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTaskGroup_NewTaskGroup(t *testing.T) {
|
||||
grp := NewTaskGroup("grp1", 2)
|
||||
expect := &TaskGroup{
|
||||
Name: "grp1",
|
||||
Count: 2,
|
||||
}
|
||||
if !reflect.DeepEqual(grp, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, grp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskGroup_Constrain(t *testing.T) {
|
||||
grp := NewTaskGroup("grp1", 1)
|
||||
|
||||
// Add a constraint to the group
|
||||
out := grp.Constrain(HardConstraint("kernel.name", "=", "darwin"))
|
||||
if n := len(grp.Constraints); n != 1 {
|
||||
t.Fatalf("expected 1 constraint, got: %d", n)
|
||||
}
|
||||
|
||||
// Check that the group was returned
|
||||
if out != grp {
|
||||
t.Fatalf("expected: %#v, got: %#v", grp, out)
|
||||
}
|
||||
|
||||
// Add a second constraint
|
||||
grp.Constrain(SoftConstraint("memory.totalbytes", ">=", "128000000", 2))
|
||||
expect := []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "darwin",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
&Constraint{
|
||||
Hard: false,
|
||||
LTarget: "memory.totalbytes",
|
||||
RTarget: "128000000",
|
||||
Operand: ">=",
|
||||
Weight: 2,
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(grp.Constraints, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, grp.Constraints)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskGroup_SetMeta(t *testing.T) {
|
||||
grp := NewTaskGroup("grp1", 1)
|
||||
|
||||
// Initializes an empty map
|
||||
out := grp.SetMeta("foo", "bar")
|
||||
if grp.Meta == nil {
|
||||
t.Fatalf("should be initialized")
|
||||
}
|
||||
|
||||
// Check that we returned the group
|
||||
if out != grp {
|
||||
t.Fatalf("expect: %#v, got: %#v", grp, out)
|
||||
}
|
||||
|
||||
// Add a second meta k/v
|
||||
grp.SetMeta("baz", "zip")
|
||||
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
||||
if !reflect.DeepEqual(grp.Meta, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, grp.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskGroup_AddTask(t *testing.T) {
|
||||
grp := NewTaskGroup("grp1", 1)
|
||||
|
||||
// Add the task to the task group
|
||||
out := grp.AddTask(NewTask("task1", "java"))
|
||||
if n := len(grp.Tasks); n != 1 {
|
||||
t.Fatalf("expected 1 task, got: %d", n)
|
||||
}
|
||||
|
||||
// Check that we returned the group
|
||||
if out != grp {
|
||||
t.Fatalf("expect: %#v, got: %#v", grp, out)
|
||||
}
|
||||
|
||||
// Add a second task
|
||||
grp.AddTask(NewTask("task2", "exec"))
|
||||
expect := []*Task{
|
||||
&Task{
|
||||
Name: "task1",
|
||||
Driver: "java",
|
||||
},
|
||||
&Task{
|
||||
Name: "task2",
|
||||
Driver: "exec",
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(grp.Tasks, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, grp.Tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_NewTask(t *testing.T) {
|
||||
task := NewTask("task1", "exec")
|
||||
expect := &Task{
|
||||
Name: "task1",
|
||||
Driver: "exec",
|
||||
}
|
||||
if !reflect.DeepEqual(task, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, task)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_SetConfig(t *testing.T) {
|
||||
task := NewTask("task1", "exec")
|
||||
|
||||
// Initializes an empty map
|
||||
out := task.SetConfig("foo", "bar")
|
||||
if task.Config == nil {
|
||||
t.Fatalf("should be initialized")
|
||||
}
|
||||
|
||||
// Check that we returned the task
|
||||
if out != task {
|
||||
t.Fatalf("expect: %#v, got: %#v", task, out)
|
||||
}
|
||||
|
||||
// Set another config value
|
||||
task.SetConfig("baz", "zip")
|
||||
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
||||
if !reflect.DeepEqual(task.Config, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, task.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_SetMeta(t *testing.T) {
|
||||
task := NewTask("task1", "exec")
|
||||
|
||||
// Initializes an empty map
|
||||
out := task.SetMeta("foo", "bar")
|
||||
if task.Meta == nil {
|
||||
t.Fatalf("should be initialized")
|
||||
}
|
||||
|
||||
// Check that we returned the task
|
||||
if out != task {
|
||||
t.Fatalf("expect: %#v, got: %#v", task, out)
|
||||
}
|
||||
|
||||
// Set another meta k/v
|
||||
task.SetMeta("baz", "zip")
|
||||
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
||||
if !reflect.DeepEqual(task.Meta, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, task.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_Require(t *testing.T) {
|
||||
task := NewTask("task1", "exec")
|
||||
|
||||
// Create some require resources
|
||||
resources := &Resources{
|
||||
CPU: 1.25,
|
||||
MemoryMB: 128,
|
||||
DiskMB: 2048,
|
||||
IOPS: 1024,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{80, 443},
|
||||
},
|
||||
},
|
||||
}
|
||||
out := task.Require(resources)
|
||||
if !reflect.DeepEqual(task.Resources, resources) {
|
||||
t.Fatalf("expect: %#v, got: %#v", resources, task.Resources)
|
||||
}
|
||||
|
||||
// Check that we returned the task
|
||||
if out != task {
|
||||
t.Fatalf("expect: %#v, got: %#v", task, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_Constrain(t *testing.T) {
|
||||
task := NewTask("task1", "exec")
|
||||
|
||||
// Add a constraint to the task
|
||||
out := task.Constrain(HardConstraint("kernel.name", "=", "darwin"))
|
||||
if n := len(task.Constraints); n != 1 {
|
||||
t.Fatalf("expected 1 constraint, got: %d", n)
|
||||
}
|
||||
|
||||
// Check that the task was returned
|
||||
if out != task {
|
||||
t.Fatalf("expected: %#v, got: %#v", task, out)
|
||||
}
|
||||
|
||||
// Add a second constraint
|
||||
task.Constrain(SoftConstraint("memory.totalbytes", ">=", "128000000", 2))
|
||||
expect := []*Constraint{
|
||||
&Constraint{
|
||||
Hard: true,
|
||||
LTarget: "kernel.name",
|
||||
RTarget: "darwin",
|
||||
Operand: "=",
|
||||
Weight: 0,
|
||||
},
|
||||
&Constraint{
|
||||
Hard: false,
|
||||
LTarget: "memory.totalbytes",
|
||||
RTarget: "128000000",
|
||||
Operand: ">=",
|
||||
Weight: 2,
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(task.Constraints, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, task.Constraints)
|
||||
}
|
||||
}
|
35
api/util_test.go
Normal file
35
api/util_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertQueryMeta(t *testing.T, qm *QueryMeta) {
|
||||
if qm.LastIndex == 0 {
|
||||
t.Fatalf("bad index: %d", qm.LastIndex)
|
||||
}
|
||||
if qm.RequestTime == 0 {
|
||||
t.Fatalf("bad request time: %d", qm.RequestTime)
|
||||
}
|
||||
if !qm.KnownLeader {
|
||||
t.Fatalf("expected known leader, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func assertWriteMeta(t *testing.T, wm *WriteMeta) {
|
||||
if wm.LastIndex == 0 {
|
||||
t.Fatalf("bad index: %d", wm.LastIndex)
|
||||
}
|
||||
if wm.RequestTime == 0 {
|
||||
t.Fatalf("bad request time: %d", wm.RequestTime)
|
||||
}
|
||||
}
|
||||
|
||||
func testJob() *Job {
|
||||
return &Job{
|
||||
ID: "job1",
|
||||
Name: "redis",
|
||||
Type: JobTypeService,
|
||||
Priority: 1,
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ go get \
|
|||
# Build!
|
||||
echo "--> Building..."
|
||||
go build \
|
||||
-ldflags "${CGO_LDFLAGS} -X main.GitCommit ${GIT_COMMIT}${GIT_DIRTY} -X main.GitDescribe ${GIT_DESCRIBE}" \
|
||||
-ldflags "${CGO_LDFLAGS} -X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X main.GitDescribe=${GIT_DESCRIBE}" \
|
||||
-v \
|
||||
-o bin/nomad${EXTENSION}
|
||||
cp bin/nomad${EXTENSION} ${GOPATHSINGLE}/bin
|
||||
|
|
Loading…
Reference in a new issue