Merge branch 'master' into vagrant
* master: (57 commits) api: use stub structs README fillin nomad: fixing unit tests scheduler: pass failure reason to ExhaustedNode nomad: thread alloc fit failure reason through nomad: rename region1 to global. Fixes #41 client: Use Alloc.TaskResouces to override Task.Resources scheduler: in-place update should preserve network offer scheduler: track dimension of exhaustion schedule: avoid in-place update of task if network resources are different scheduler: expose reason network offer failed nomad: adding reason network offer failed mock: use network resources scheduler: thread through the TaskResources nomad: removing old network index lookup methods nomad: Resource Superset ignores network in favor of NetworkIndex nomad: update for new AllocsFit API nomad: remove PortsOvercommited in favor of NetworkIndex scheduler: use the new network index nomad: exposing IntContains ...
13
Makefile
@@ -2,6 +2,12 @@ DEPS = $(shell go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
|
||||
PACKAGES = $(shell go list ./...)
|
||||
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods \
|
||||
-nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
||||
EXTERNAL_TOOLS=\
|
||||
github.com/tools/godep \
|
||||
github.com/mitchellh/gox \
|
||||
golang.org/x/tools/cmd/cover \
|
||||
golang.org/x/tools/cmd/vet
|
||||
|
||||
|
||||
all: deps format
|
||||
@mkdir -p bin/
|
||||
@@ -50,4 +56,11 @@ web:
|
||||
web-push:
|
||||
./scripts/website_push.sh
|
||||
|
||||
# bootstrap the build by downloading additional tools
|
||||
bootstrap:
|
||||
@for tool in $(EXTERNAL_TOOLS) ; do \
|
||||
echo "Installing $$tool" ; \
|
||||
go get $$tool; \
|
||||
done
|
||||
|
||||
.PHONY: all cov deps integ test vet web web-push test-nodep
|
||||
|
||||
91
README.md
@@ -1,2 +1,89 @@
|
||||
# nomad
|
||||
Where the wild bits roam
|
||||
Nomad [](https://travis-ci.org/hashicorp/nomad)
|
||||
=========
|
||||
|
||||
- Website: https://www.nomadproject.io
|
||||
- IRC: `#nomad-tool` on Freenode
|
||||
- Mailing list: [Google Groups](https://groups.google.com/group/nomad-tool)
|
||||
|
||||

|
||||
|
||||
Nomad is a cluster manager, designed for both long lived services and short
|
||||
lived batch processing workloads. Developers use a declarative job specification
|
||||
to submit work, and Nomad ensures constraints are satisfied and resource utilization
|
||||
is optimized by efficient task packing. Nomad supports all major operating systems
|
||||
and virtualized, containerized, or standalone applications.
|
||||
|
||||
The key features of Nomad are:
|
||||
|
||||
* **Docker Support**: Jobs can specify tasks which are Docker containers.
|
||||
Nomad will automatically run the containers on clients which have Docker
|
||||
installed, scale up and down based on the number of instances request,
|
||||
and automatically recover from failures.
|
||||
|
||||
* **Multi-Datacenter and Multi-Region Aware**: Nomad is designed to be
|
||||
a global-scale scheduler. Multiple datacenters can be managed as part
|
||||
of a larger region, and jobs can be scheduled across datacenters if
|
||||
requested. Multiple regions join together and federate jobs making it
|
||||
easy to run jobs anywhere.
|
||||
|
||||
* **Operationally Simple**: Nomad runs as a single binary that can be
|
||||
either a client or server, and is completely self contained. Nomad does
|
||||
not require any external services for storage or coordination. This means
|
||||
Nomad combines the features of a resource manager and scheduler in a single
|
||||
system.
|
||||
|
||||
* **Distributed and Highly-Available**: Nomad servers cluster together and
|
||||
perform leader election and state replication to provide high availability
|
||||
in the face of failure. The Nomad scheduling engine is optimized for
|
||||
optimistic concurrency allowing all servers to make scheduling decisions to
|
||||
maximize throughput.
|
||||
|
||||
* **HashiCorp Ecosystem**: Nomad integrates with the entire HashiCorp
|
||||
ecosystem of tools. Along with all HashiCorp tools, Nomad is designed
|
||||
in the unix philosophy of doing something specific and doing it well.
|
||||
Nomad integrates with tools like Packer, Consul, and Terraform to support
|
||||
building artifacts, service discovery, monitoring and capacity management.
|
||||
|
||||
For more information, see the [introduction section](https://www.nomadproject.io/intro)
|
||||
of the Nomad website.
|
||||
|
||||
Getting Started & Documentation
|
||||
-------------------------------
|
||||
|
||||
All documentation is available on the [Nomad website](https://www.nomadproject.io).
|
||||
|
||||
Developing Nomad
|
||||
--------------------
|
||||
|
||||
If you wish to work on Nomad itself or any of its built-in systems,
|
||||
you'll first need [Go](https://www.golang.org) installed on your
|
||||
machine (version 1.4+ is *required*).
|
||||
|
||||
For local dev first make sure Go is properly installed, including setting up a
|
||||
[GOPATH](https://golang.org/doc/code.html#GOPATH). After setting up Go, you can
|
||||
download the required build tools such as vet, cover, godep etc by bootstrapping
|
||||
your environment.
|
||||
|
||||
```sh
|
||||
$ make bootstrap
|
||||
...
|
||||
```
|
||||
|
||||
Next, clone this repository into `$GOPATH/src/github.com/hashicorp/nomad`.
|
||||
Then type `make test`. This will run the tests. If this exits with exit status 0,
|
||||
then everything is working!
|
||||
|
||||
```sh
|
||||
$ make test
|
||||
...
|
||||
```
|
||||
|
||||
To compile a development version of Nomad, run `make`. This will put the
|
||||
Nomad binary in the `bin` and `$GOPATH/bin` folders:
|
||||
|
||||
```sh
|
||||
$ make
|
||||
...
|
||||
$ bin/nomad
|
||||
...
|
||||
```
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Allocations is used to query the alloc-related endpoints.
|
||||
type Allocations struct {
|
||||
client *Client
|
||||
@@ -11,8 +15,8 @@ func (c *Client) Allocations() *Allocations {
|
||||
}
|
||||
|
||||
// List returns a list of all of the allocations.
|
||||
func (a *Allocations) List(q *QueryOptions) ([]*Allocation, *QueryMeta, error) {
|
||||
var resp []*Allocation
|
||||
func (a *Allocations) List(q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
|
||||
var resp []*AllocationListStub
|
||||
qm, err := a.client.query("/v1/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -32,6 +36,41 @@ func (a *Allocations) Info(allocID string, q *QueryOptions) (*Allocation, *Query
|
||||
|
||||
// Allocation is used for serialization of allocations.
|
||||
type Allocation struct {
|
||||
ID string
|
||||
EvalID string
|
||||
Name string
|
||||
NodeID string
|
||||
JobID string
|
||||
Job *Job
|
||||
TaskGroup string
|
||||
Resources *Resources
|
||||
TaskResources map[string]*Resources
|
||||
Metrics *AllocationMetric
|
||||
DesiredStatus string
|
||||
DesiredDescription string
|
||||
ClientStatus string
|
||||
ClientDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// AllocationMetric is used to deserialize allocation metrics.
|
||||
type AllocationMetric struct {
|
||||
NodesEvaluated int
|
||||
NodesFiltered int
|
||||
ClassFiltered map[string]int
|
||||
ConstraintFiltered map[string]int
|
||||
NodesExhausted int
|
||||
ClassExhausted map[string]int
|
||||
DimensionExhaused map[string]int
|
||||
Scores map[string]float64
|
||||
AllocationTime time.Duration
|
||||
CoalescedFailures int
|
||||
}
|
||||
|
||||
// AllocationListStub is used to return a subset of an allocation
|
||||
// during list operations.
|
||||
type AllocationListStub struct {
|
||||
ID string
|
||||
EvalID string
|
||||
Name string
|
||||
@@ -42,4 +81,6 @@ type Allocation struct {
|
||||
DesiredDescription string
|
||||
ClientStatus string
|
||||
ClientDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ type configCallback func(c *Config)
|
||||
|
||||
func makeClient(t *testing.T, cb1 configCallback,
|
||||
cb2 testutil.ServerConfigCallback) (*Client, *testutil.TestServer) {
|
||||
// Always run these tests in parallel
|
||||
t.Parallel()
|
||||
|
||||
// Make client config
|
||||
conf := DefaultConfig()
|
||||
@@ -48,7 +50,6 @@ func TestDefaultConfig_env(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetQueryOptions(t *testing.T) {
|
||||
// TODO t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
@@ -76,7 +77,6 @@ func TestSetQueryOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetWriteOptions(t *testing.T) {
|
||||
// TODO t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
@@ -92,7 +92,6 @@ func TestSetWriteOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRequestToHTTP(t *testing.T) {
|
||||
// TODO t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
@@ -157,7 +156,6 @@ func TestParseWriteMeta(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQueryString(t *testing.T) {
|
||||
// TODO t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ func (e *Evaluations) Info(evalID string, q *QueryOptions) (*Evaluation, *QueryM
|
||||
|
||||
// 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
|
||||
func (e *Evaluations) Allocations(evalID string, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
|
||||
var resp []*AllocationListStub
|
||||
qm, err := e.client.query("/v1/evaluation/"+evalID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
23
api/jobs.go
@@ -32,8 +32,8 @@ func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
}
|
||||
|
||||
// List is used to list all of the existing jobs.
|
||||
func (j *Jobs) List(q *QueryOptions) ([]*Job, *QueryMeta, error) {
|
||||
var resp []*Job
|
||||
func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
|
||||
var resp []*JobListStub
|
||||
qm, err := j.client.query("/v1/jobs", &resp, q)
|
||||
if err != nil {
|
||||
return nil, qm, err
|
||||
@@ -53,8 +53,8 @@ func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
|
||||
}
|
||||
|
||||
// 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
|
||||
func (j *Jobs) Allocations(jobID string, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
|
||||
var resp []*AllocationListStub
|
||||
qm, err := j.client.query("/v1/job/"+jobID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -105,6 +105,21 @@ type Job struct {
|
||||
Meta map[string]string
|
||||
Status string
|
||||
StatusDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// JobListStub is used to return a subset of information about
|
||||
// jobs during list operations.
|
||||
type JobListStub struct {
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Priority int
|
||||
Status string
|
||||
StatusDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// NewServiceJob creates and returns a new service-style job
|
||||
|
||||
@@ -42,8 +42,7 @@ func TestJobs_Register(t *testing.T) {
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that we got the expected response
|
||||
expect := []*Job{job}
|
||||
if !reflect.DeepEqual(resp, expect) {
|
||||
if len(resp) != 1 || resp[0].ID != job.ID {
|
||||
t.Fatalf("bad: %#v", resp[0])
|
||||
}
|
||||
}
|
||||
@@ -76,7 +75,7 @@ func TestJobs_Info(t *testing.T) {
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that the result is what we expect
|
||||
if !reflect.DeepEqual(result, job) {
|
||||
if result == nil || result.ID != job.ID {
|
||||
t.Fatalf("expect: %#v, got: %#v", job, result)
|
||||
}
|
||||
}
|
||||
|
||||
101
api/nodes.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Nodes is used to query node-related API endpoints
|
||||
type Nodes struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Nodes returns a handle on the node endpoints.
|
||||
func (c *Client) Nodes() *Nodes {
|
||||
return &Nodes{client: c}
|
||||
}
|
||||
|
||||
// List is used to list out all of the nodes
|
||||
func (n *Nodes) List(q *QueryOptions) ([]*NodeListStub, *QueryMeta, error) {
|
||||
var resp []*NodeListStub
|
||||
qm, err := n.client.query("/v1/nodes", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Info is used to query a specific node by its ID.
|
||||
func (n *Nodes) Info(nodeID string, q *QueryOptions) (*Node, *QueryMeta, error) {
|
||||
var resp Node
|
||||
qm, err := n.client.query("/v1/node/"+nodeID, &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// ToggleDrain is used to toggle drain mode on/off for a given node.
|
||||
func (n *Nodes) ToggleDrain(nodeID string, drain bool, q *WriteOptions) (*WriteMeta, error) {
|
||||
drainArg := strconv.FormatBool(drain)
|
||||
wm, err := n.client.write("/v1/node/"+nodeID+"/drain?enable="+drainArg, nil, nil, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Allocations is used to return the allocations associated with a node.
|
||||
func (n *Nodes) Allocations(nodeID string, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
|
||||
var resp []*AllocationListStub
|
||||
qm, err := n.client.query("/v1/node/"+nodeID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// ForceEvaluate is used to force-evaluate an existing node.
|
||||
func (n *Nodes) ForceEvaluate(nodeID string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp nodeEvalResponse
|
||||
wm, err := n.client.write("/v1/node/"+nodeID+"/evaluate", nil, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// Node is used to deserialize a node entry.
|
||||
type Node struct {
|
||||
ID string
|
||||
Datacenter string
|
||||
Name string
|
||||
Attributes map[string]string
|
||||
Resources *Resources
|
||||
Reserved *Resources
|
||||
Links map[string]string
|
||||
NodeClass string
|
||||
Drain bool
|
||||
Status string
|
||||
StatusDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// NodeListStub is a subset of information returned during
|
||||
// node list operations.
|
||||
type NodeListStub struct {
|
||||
ID string
|
||||
Datacenter string
|
||||
Name string
|
||||
NodeClass string
|
||||
Drain bool
|
||||
Status string
|
||||
StatusDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// nodeEvalResponse is used to decode a force-eval.
|
||||
type nodeEvalResponse struct {
|
||||
EvalID string
|
||||
}
|
||||
203
api/nodes_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
)
|
||||
|
||||
func TestNodes_List(t *testing.T) {
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.DevMode = true
|
||||
})
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
var qm *QueryMeta
|
||||
var out []*NodeListStub
|
||||
var err error
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, qm, err = nodes.List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n := len(out); n != 1 {
|
||||
return false, fmt.Errorf("expected 1 node, got: %d", n)
|
||||
}
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Check that we got valid QueryMeta.
|
||||
assertQueryMeta(t, qm)
|
||||
}
|
||||
|
||||
func TestNodes_Info(t *testing.T) {
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.DevMode = true
|
||||
})
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Retrieving a non-existent node returns error
|
||||
_, _, err := nodes.Info("nope", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Get the node ID
|
||||
var nodeID, dc string
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := nodes.List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n := len(out); n != 1 {
|
||||
return false, fmt.Errorf("expected 1 node, got: %d", n)
|
||||
}
|
||||
nodeID = out[0].ID
|
||||
dc = out[0].Datacenter
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Querying for existing nodes returns properly
|
||||
result, qm, err := nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that the result is what we expect
|
||||
if result.ID != nodeID || result.Datacenter != dc {
|
||||
t.Fatalf("expected %s (%s), got: %s (%s)",
|
||||
nodeID, dc,
|
||||
result.ID, result.Datacenter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_ToggleDrain(t *testing.T) {
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.DevMode = true
|
||||
})
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Wait for node registration and get the ID
|
||||
var nodeID string
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := nodes.List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n := len(out); n != 1 {
|
||||
return false, fmt.Errorf("expected 1 node, got: %d", n)
|
||||
}
|
||||
nodeID = out[0].ID
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Check for drain mode
|
||||
out, _, err := nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.Drain {
|
||||
t.Fatalf("drain mode should be off")
|
||||
}
|
||||
|
||||
// Toggle it on
|
||||
wm, err := nodes.ToggleDrain(nodeID, true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Check again
|
||||
out, _, err = nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !out.Drain {
|
||||
t.Fatalf("drain mode should be on")
|
||||
}
|
||||
|
||||
// Toggle off again
|
||||
wm, err = nodes.ToggleDrain(nodeID, false, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Check again
|
||||
out, _, err = nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.Drain {
|
||||
t.Fatalf("drain mode should be off")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_Allocations(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Looking up by a non-existent node returns nothing. We
|
||||
// don't check the index here because it's possible the node
|
||||
// has already registered, in which case we will get a non-
|
||||
// zero result anyways.
|
||||
allocs, _, err := nodes.Allocations("nope", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if n := len(allocs); n != 0 {
|
||||
t.Fatalf("expected 0 allocs, got: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_ForceEvaluate(t *testing.T) {
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.DevMode = true
|
||||
})
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Force-eval on a non-existent node fails
|
||||
_, _, err := nodes.ForceEvaluate("nope", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Wait for node registration and get the ID
|
||||
var nodeID string
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := nodes.List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n := len(out); n != 1 {
|
||||
return false, fmt.Errorf("expected 1 node, got: %d", n)
|
||||
}
|
||||
nodeID = out[0].ID
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Try force-eval again. We don't check the WriteMeta because
|
||||
// there are no allocations to process, so we would get an index
|
||||
// of zero. Same goes for the eval ID.
|
||||
_, _, err = nodes.ForceEvaluate(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
32
api/status.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
// Status is used to query the status-related endpoints.
|
||||
type Status struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Status returns a handle on the status endpoints.
|
||||
func (c *Client) Status() *Status {
|
||||
return &Status{client: c}
|
||||
}
|
||||
|
||||
// Leader is used to query for the current cluster leader.
|
||||
func (s *Status) Leader() (string, error) {
|
||||
var resp string
|
||||
_, err := s.client.query("/v1/status/leader", &resp, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Peers is used to query the addresses of the server peers
|
||||
// in the cluster.
|
||||
func (s *Status) Peers() ([]string, error) {
|
||||
var resp []string
|
||||
_, err := s.client.query("/v1/status/peers", &resp, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
20
api/status_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatus_Leader(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
status := c.Status()
|
||||
|
||||
// Query for leader status should return a result
|
||||
out, err := status.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out == "" {
|
||||
t.Fatalf("expected leader, got: %q", out)
|
||||
}
|
||||
}
|
||||
@@ -288,6 +288,10 @@ func (r *AllocRunner) Run() {
|
||||
if _, ok := r.tasks[task.Name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Merge in the task resources
|
||||
task.Resources = alloc.TaskResources[task.Name]
|
||||
|
||||
tr := NewTaskRunner(r.logger, r.config, r.setTaskStatus, r.ctx, r.alloc.ID, task)
|
||||
r.tasks[task.Name] = tr
|
||||
go tr.Run()
|
||||
@@ -309,6 +313,9 @@ OUTER:
|
||||
r.taskLock.RLock()
|
||||
for _, task := range tg.Tasks {
|
||||
tr := r.tasks[task.Name]
|
||||
|
||||
// Merge in the task resources
|
||||
task.Resources = update.TaskResources[task.Name]
|
||||
tr.Update(task)
|
||||
}
|
||||
r.taskLock.RUnlock()
|
||||
|
||||
@@ -52,7 +52,7 @@ const (
|
||||
func DefaultConfig() *config.Config {
|
||||
return &config.Config{
|
||||
LogOutput: os.Stderr,
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ func NewClient(cfg *config.Config) (*Client, error) {
|
||||
shutdownCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Initialize the client
|
||||
if err := c.init(); err != nil {
|
||||
return nil, fmt.Errorf("failed intializing client: %v", err)
|
||||
}
|
||||
|
||||
// Restore the state
|
||||
if err := c.restoreState(); err != nil {
|
||||
return nil, fmt.Errorf("failed to restore state: %v", err)
|
||||
@@ -123,6 +128,18 @@ func NewClient(cfg *config.Config) (*Client, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// init is used to initialize the client and perform any setup
|
||||
// needed before we begin starting its various components.
|
||||
func (c *Client) init() error {
|
||||
// Ensure the alloc dir exists if we have one
|
||||
if c.config.AllocDir != "" {
|
||||
if err := os.MkdirAll(c.config.AllocDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed creating alloc dir: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Leave is used to prepare the client to leave the cluster
|
||||
func (c *Client) Leave() error {
|
||||
// TODO
|
||||
|
||||
@@ -2,7 +2,10 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -155,7 +158,7 @@ func TestClient_Register(t *testing.T) {
|
||||
|
||||
req := structs.NodeSpecificRequest{
|
||||
NodeID: c1.Node().ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var out structs.SingleNodeResponse
|
||||
|
||||
@@ -188,7 +191,7 @@ func TestClient_Heartbeat(t *testing.T) {
|
||||
|
||||
req := structs.NodeSpecificRequest{
|
||||
NodeID: c1.Node().ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var out structs.SingleNodeResponse
|
||||
|
||||
@@ -365,3 +368,25 @@ func TestClient_SaveRestoreState(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", ar.Alloc())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Init(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "nomad")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
allocDir := filepath.Join(dir, "alloc")
|
||||
|
||||
client := &Client{
|
||||
config: &config.Config{
|
||||
AllocDir: allocDir,
|
||||
},
|
||||
}
|
||||
if err := client.init(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(allocDir); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,9 @@ func persistState(path string, data interface{}) error {
|
||||
func restoreState(path string, data interface{}) error {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read state: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(buf, data); err != nil {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -80,6 +82,16 @@ func TestShuffleStrings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPersistRestoreState(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "nomad")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Use a state path inside a non-existent directory. This
|
||||
// verifies that the directory is created properly.
|
||||
statePath := filepath.Join(dir, "subdir", "test-persist")
|
||||
|
||||
type stateTest struct {
|
||||
Foo int
|
||||
Bar string
|
||||
@@ -90,15 +102,14 @@ func TestPersistRestoreState(t *testing.T) {
|
||||
Bar: "the quick brown fox",
|
||||
Baz: true,
|
||||
}
|
||||
defer os.Remove("test-persist")
|
||||
|
||||
err := persistState("test-persist", &state)
|
||||
err = persistState(statePath, &state)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
var out stateTest
|
||||
err = restoreState("test-persist", &out)
|
||||
err = restoreState(statePath, &out)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
@@ -58,13 +58,9 @@ func NewAgent(config *Config, logOutput io.Writer) (*Agent, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// setupServer is used to setup the server if enabled
|
||||
func (a *Agent) setupServer() error {
|
||||
if !a.config.Server.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup the configuration
|
||||
// serverConfig is used to generate a new server configuration struct
|
||||
// for initializing a nomad server.
|
||||
func (a *Agent) serverConfig() (*nomad.Config, error) {
|
||||
conf := a.config.NomadConfig
|
||||
if conf == nil {
|
||||
conf = nomad.DefaultConfig()
|
||||
@@ -102,19 +98,57 @@ func (a *Agent) setupServer() error {
|
||||
if len(a.config.Server.EnabledSchedulers) != 0 {
|
||||
conf.EnabledSchedulers = a.config.Server.EnabledSchedulers
|
||||
}
|
||||
if addr := a.config.Server.AdvertiseAddr; addr != "" {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
|
||||
// Set up the advertise addrs
|
||||
if addr := a.config.AdvertiseAddrs.Serf; addr != "" {
|
||||
serfAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve advertise address: %v", err)
|
||||
return nil, fmt.Errorf("error resolving serf advertise address: %s", err)
|
||||
}
|
||||
conf.RPCAdvertise = tcpAddr
|
||||
conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String()
|
||||
conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port
|
||||
}
|
||||
if addr := a.config.Server.BindAddr; addr != "" {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if addr := a.config.AdvertiseAddrs.RPC; addr != "" {
|
||||
rpcAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve bind address: %v", err)
|
||||
return nil, fmt.Errorf("error resolving rpc advertise address: %s", err)
|
||||
}
|
||||
conf.RPCAddr = tcpAddr
|
||||
conf.RPCAdvertise = rpcAddr
|
||||
}
|
||||
|
||||
// Set up the bind addresses
|
||||
if addr := a.config.BindAddr; addr != "" {
|
||||
conf.RPCAddr.IP = net.ParseIP(addr)
|
||||
conf.SerfConfig.MemberlistConfig.BindAddr = addr
|
||||
}
|
||||
if addr := a.config.Addresses.RPC; addr != "" {
|
||||
conf.RPCAddr.IP = net.ParseIP(addr)
|
||||
}
|
||||
if addr := a.config.Addresses.Serf; addr != "" {
|
||||
conf.SerfConfig.MemberlistConfig.BindAddr = addr
|
||||
}
|
||||
|
||||
// Set up the ports
|
||||
if port := a.config.Ports.RPC; port != 0 {
|
||||
conf.RPCAddr.Port = port
|
||||
}
|
||||
if port := a.config.Ports.Serf; port != 0 {
|
||||
conf.SerfConfig.MemberlistConfig.BindPort = port
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// setupServer is used to setup the server if enabled
|
||||
func (a *Agent) setupServer() error {
|
||||
if !a.config.Server.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setup the configuration
|
||||
conf, err := a.serverConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("server config setup failed: %s", err)
|
||||
}
|
||||
|
||||
// Create the server
|
||||
@@ -122,6 +156,7 @@ func (a *Agent) setupServer() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("server setup failed: %v", err)
|
||||
}
|
||||
|
||||
a.server = server
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -79,3 +80,86 @@ func TestAgent_RPCPing(t *testing.T) {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_ServerConfig(t *testing.T) {
|
||||
conf := DefaultConfig()
|
||||
a := &Agent{config: conf}
|
||||
|
||||
// Returns error on bad serf addr
|
||||
conf.AdvertiseAddrs.Serf = "nope"
|
||||
_, err := a.serverConfig()
|
||||
if err == nil || !strings.Contains(err.Error(), "serf advertise") {
|
||||
t.Fatalf("expected serf address error, got: %#v", err)
|
||||
}
|
||||
conf.AdvertiseAddrs.Serf = "127.0.0.1:4000"
|
||||
|
||||
// Returns error on bad rpc addr
|
||||
conf.AdvertiseAddrs.RPC = "nope"
|
||||
_, err = a.serverConfig()
|
||||
if err == nil || !strings.Contains(err.Error(), "rpc advertise") {
|
||||
t.Fatalf("expected rpc address error, got: %#v", err)
|
||||
}
|
||||
conf.AdvertiseAddrs.RPC = "127.0.0.1:4001"
|
||||
|
||||
// Parses the advertise addrs correctly
|
||||
out, err := a.serverConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
serfAddr := out.SerfConfig.MemberlistConfig.AdvertiseAddr
|
||||
if serfAddr != "127.0.0.1" {
|
||||
t.Fatalf("expect 127.0.0.1, got: %s", serfAddr)
|
||||
}
|
||||
serfPort := out.SerfConfig.MemberlistConfig.AdvertisePort
|
||||
if serfPort != 4000 {
|
||||
t.Fatalf("expected 4000, got: %d", serfPort)
|
||||
}
|
||||
if addr := out.RPCAdvertise; addr.IP.String() != "127.0.0.1" || addr.Port != 4001 {
|
||||
t.Fatalf("bad rpc advertise addr: %#v", addr)
|
||||
}
|
||||
|
||||
// Sets up the ports properly
|
||||
conf.Ports.RPC = 4003
|
||||
conf.Ports.Serf = 4004
|
||||
|
||||
out, err = a.serverConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if addr := out.RPCAddr.Port; addr != 4003 {
|
||||
t.Fatalf("expect 4003, got: %d", out.RPCAddr.Port)
|
||||
}
|
||||
if port := out.SerfConfig.MemberlistConfig.BindPort; port != 4004 {
|
||||
t.Fatalf("expect 4004, got: %d", port)
|
||||
}
|
||||
|
||||
// Prefers the most specific bind addrs
|
||||
conf.BindAddr = "127.0.0.3"
|
||||
conf.Addresses.RPC = "127.0.0.2"
|
||||
conf.Addresses.Serf = "127.0.0.2"
|
||||
|
||||
out, err = a.serverConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if addr := out.RPCAddr.IP.String(); addr != "127.0.0.2" {
|
||||
t.Fatalf("expect 127.0.0.2, got: %s", addr)
|
||||
}
|
||||
if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.2" {
|
||||
t.Fatalf("expect 127.0.0.2, got: %s", addr)
|
||||
}
|
||||
|
||||
// Defaults to the global bind addr
|
||||
conf.Addresses.RPC = ""
|
||||
conf.Addresses.Serf = ""
|
||||
out, err = a.serverConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if addr := out.RPCAddr.IP.String(); addr != "127.0.0.3" {
|
||||
t.Fatalf("expect 127.0.0.3, got: %s", addr)
|
||||
}
|
||||
if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.3" {
|
||||
t.Fatalf("expect 127.0.0.3, got: %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
// Config is the configuration for the Nomad agent.
|
||||
type Config struct {
|
||||
// Region is the region this agent is in. Defaults to region1.
|
||||
// Region is the region this agent is in. Defaults to global.
|
||||
Region string `hcl:"region"`
|
||||
|
||||
// Datacenter is the datacenter this agent is in. Defaults to dc1
|
||||
@@ -31,13 +32,22 @@ type Config struct {
|
||||
// LogLevel is the level of the logs to putout
|
||||
LogLevel string `hcl:"log_level"`
|
||||
|
||||
// HttpAddr is used to control the address and port we bind to.
|
||||
// If not specified, 127.0.0.1:4646 is used.
|
||||
HttpAddr string `hcl:"http_addr"`
|
||||
// BindAddr is the address on which all of nomad's services will
|
||||
// be bound. If not specified, this defaults to 127.0.0.1.
|
||||
BindAddr string `hcl:"bind_addr"`
|
||||
|
||||
// EnableDebug is used to enable debugging HTTP endpoints
|
||||
EnableDebug bool `hcl:"enable_debug"`
|
||||
|
||||
// Ports is used to control the network ports we bind to.
|
||||
Ports *Ports `hcl:"ports"`
|
||||
|
||||
// Addresses is used to override the network addresses we bind to.
|
||||
Addresses *Addresses `hcl:"addresses"`
|
||||
|
||||
// AdvertiseAddrs is used to control the addresses we advertise.
|
||||
AdvertiseAddrs *AdvertiseAddrs `hcl:"advertise"`
|
||||
|
||||
// Client has our client related settings
|
||||
Client *ClientConfig `hcl:"client"`
|
||||
|
||||
@@ -112,16 +122,6 @@ type ServerConfig struct {
|
||||
// ProtocolVersionMin and ProtocolVersionMax.
|
||||
ProtocolVersion int `hcl:"protocol_version"`
|
||||
|
||||
// AdvertiseAddr is the address we use for advertising our Serf,
|
||||
// and Consul RPC IP. If not specified, bind address is used.
|
||||
AdvertiseAddr string `mapstructure:"advertise_addr"`
|
||||
|
||||
// BindAddr is used to control the address we bind to.
|
||||
// If not specified, the first private IP we find is used.
|
||||
// This controls the address we use for cluster facing
|
||||
// services (Gossip, Server RPC)
|
||||
BindAddr string `hcl:"bind_addr"`
|
||||
|
||||
// NumSchedulers is the number of scheduler thread that are run.
|
||||
// This can be as many as one per core, or zero to disable this server
|
||||
// from doing any scheduling work.
|
||||
@@ -140,6 +140,30 @@ type Telemetry struct {
|
||||
DisableHostname bool `hcl:"disable_hostname"`
|
||||
}
|
||||
|
||||
// Ports is used to encapsulate the various ports we bind to for network
|
||||
// services. If any are not specified then the defaults are used instead.
|
||||
type Ports struct {
|
||||
HTTP int `hcl:"http"`
|
||||
RPC int `hcl:"rpc"`
|
||||
Serf int `hcl:"serf"`
|
||||
}
|
||||
|
||||
// Addresses encapsulates all of the addresses we bind to for various
|
||||
// network services. Everything is optional and defaults to BindAddr.
|
||||
type Addresses struct {
|
||||
HTTP string `hcl:"http"`
|
||||
RPC string `hcl:"rpc"`
|
||||
Serf string `hcl:"serf"`
|
||||
}
|
||||
|
||||
// AdvertiseAddrs is used to control the addresses we advertise out for
|
||||
// different network services. Not all network services support an
|
||||
// advertise address. All are optional and default to BindAddr.
|
||||
type AdvertiseAddrs struct {
|
||||
RPC string `hcl:"rpc"`
|
||||
Serf string `hcl:"serf"`
|
||||
}
|
||||
|
||||
// DevConfig is a Config that is used for dev mode of Nomad.
|
||||
func DevConfig() *Config {
|
||||
conf := DefaultConfig()
|
||||
@@ -150,21 +174,22 @@ func DevConfig() *Config {
|
||||
conf.EnableDebug = true
|
||||
conf.DisableAnonymousSignature = true
|
||||
return conf
|
||||
return &Config{
|
||||
LogLevel: "DEBUG",
|
||||
DevMode: true,
|
||||
EnableDebug: true,
|
||||
DisableAnonymousSignature: true,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultConfig is a the baseline configuration for Nomad
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
LogLevel: "INFO",
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
Datacenter: "dc1",
|
||||
HttpAddr: "127.0.0.1:4646",
|
||||
BindAddr: "127.0.0.1",
|
||||
Ports: &Ports{
|
||||
HTTP: 4646,
|
||||
RPC: 4647,
|
||||
Serf: 4648,
|
||||
},
|
||||
Addresses: &Addresses{},
|
||||
AdvertiseAddrs: &AdvertiseAddrs{},
|
||||
Client: &ClientConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
@@ -174,6 +199,15 @@ func DefaultConfig() *Config {
|
||||
}
|
||||
}
|
||||
|
||||
// GetListener can be used to get a new listener using a custom bind address.
|
||||
// If the bind provided address is empty, the BindAddr is used instead.
|
||||
func (c *Config) Listener(proto, addr string, port int) (net.Listener, error) {
|
||||
if addr == "" {
|
||||
addr = c.BindAddr
|
||||
}
|
||||
return net.Listen(proto, fmt.Sprintf("%s:%d", addr, port))
|
||||
}
|
||||
|
||||
// Merge merges two configurations.
|
||||
func (a *Config) Merge(b *Config) *Config {
|
||||
var result Config = *a
|
||||
@@ -193,8 +227,8 @@ func (a *Config) Merge(b *Config) *Config {
|
||||
if b.LogLevel != "" {
|
||||
result.LogLevel = b.LogLevel
|
||||
}
|
||||
if b.HttpAddr != "" {
|
||||
result.HttpAddr = b.HttpAddr
|
||||
if b.BindAddr != "" {
|
||||
result.BindAddr = b.BindAddr
|
||||
}
|
||||
if b.EnableDebug {
|
||||
result.EnableDebug = true
|
||||
@@ -242,6 +276,30 @@ func (a *Config) Merge(b *Config) *Config {
|
||||
result.Server = result.Server.Merge(b.Server)
|
||||
}
|
||||
|
||||
// Apply the ports config
|
||||
if result.Ports == nil && b.Ports != nil {
|
||||
ports := *b.Ports
|
||||
result.Ports = &ports
|
||||
} else if b.Ports != nil {
|
||||
result.Ports = result.Ports.Merge(b.Ports)
|
||||
}
|
||||
|
||||
// Apply the address config
|
||||
if result.Addresses == nil && b.Addresses != nil {
|
||||
addrs := *b.Addresses
|
||||
result.Addresses = &addrs
|
||||
} else if b.Addresses != nil {
|
||||
result.Addresses = result.Addresses.Merge(b.Addresses)
|
||||
}
|
||||
|
||||
// Apply the advertise addrs config
|
||||
if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil {
|
||||
advertise := *b.AdvertiseAddrs
|
||||
result.AdvertiseAddrs = &advertise
|
||||
} else if b.AdvertiseAddrs != nil {
|
||||
result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs)
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
@@ -264,12 +322,6 @@ func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig {
|
||||
if b.ProtocolVersion != 0 {
|
||||
result.ProtocolVersion = b.ProtocolVersion
|
||||
}
|
||||
if b.AdvertiseAddr != "" {
|
||||
result.AdvertiseAddr = b.AdvertiseAddr
|
||||
}
|
||||
if b.BindAddr != "" {
|
||||
result.BindAddr = b.BindAddr
|
||||
}
|
||||
if b.NumSchedulers != 0 {
|
||||
result.NumSchedulers = b.NumSchedulers
|
||||
}
|
||||
@@ -330,6 +382,51 @@ func (a *Telemetry) Merge(b *Telemetry) *Telemetry {
|
||||
return &result
|
||||
}
|
||||
|
||||
// Merge is used to merge two port configurations.
|
||||
func (a *Ports) Merge(b *Ports) *Ports {
|
||||
var result Ports = *a
|
||||
|
||||
if b.HTTP != 0 {
|
||||
result.HTTP = b.HTTP
|
||||
}
|
||||
if b.RPC != 0 {
|
||||
result.RPC = b.RPC
|
||||
}
|
||||
if b.Serf != 0 {
|
||||
result.Serf = b.Serf
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
// Merge is used to merge two address configs together.
|
||||
func (a *Addresses) Merge(b *Addresses) *Addresses {
|
||||
var result Addresses = *a
|
||||
|
||||
if b.HTTP != "" {
|
||||
result.HTTP = b.HTTP
|
||||
}
|
||||
if b.RPC != "" {
|
||||
result.RPC = b.RPC
|
||||
}
|
||||
if b.Serf != "" {
|
||||
result.Serf = b.Serf
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
// Merge merges two advertise addrs configs together.
|
||||
func (a *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs {
|
||||
var result AdvertiseAddrs = *a
|
||||
|
||||
if b.RPC != "" {
|
||||
result.RPC = b.RPC
|
||||
}
|
||||
if b.Serf != "" {
|
||||
result.Serf = b.Serf
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration at the given path, regardless if
|
||||
// its a file or directory.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
|
||||
@@ -12,12 +12,11 @@ import (
|
||||
|
||||
func TestConfig_Merge(t *testing.T) {
|
||||
c1 := &Config{
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
Datacenter: "dc1",
|
||||
NodeName: "node1",
|
||||
DataDir: "/tmp/dir1",
|
||||
LogLevel: "INFO",
|
||||
HttpAddr: "127.0.0.1:4646",
|
||||
EnableDebug: false,
|
||||
LeaveOnInt: false,
|
||||
LeaveOnTerm: false,
|
||||
@@ -25,6 +24,7 @@ func TestConfig_Merge(t *testing.T) {
|
||||
SyslogFacility: "local0.info",
|
||||
DisableUpdateCheck: false,
|
||||
DisableAnonymousSignature: false,
|
||||
BindAddr: "127.0.0.1",
|
||||
Telemetry: &Telemetry{
|
||||
StatsiteAddr: "127.0.0.1:8125",
|
||||
StatsdAddr: "127.0.0.1:8125",
|
||||
@@ -43,10 +43,22 @@ func TestConfig_Merge(t *testing.T) {
|
||||
BootstrapExpect: 1,
|
||||
DataDir: "/tmp/data1",
|
||||
ProtocolVersion: 1,
|
||||
AdvertiseAddr: "127.0.0.1:4647",
|
||||
BindAddr: "127.0.0.1",
|
||||
NumSchedulers: 1,
|
||||
},
|
||||
Ports: &Ports{
|
||||
HTTP: 4646,
|
||||
RPC: 4647,
|
||||
Serf: 4648,
|
||||
},
|
||||
Addresses: &Addresses{
|
||||
HTTP: "127.0.0.1",
|
||||
RPC: "127.0.0.1",
|
||||
Serf: "127.0.0.1",
|
||||
},
|
||||
AdvertiseAddrs: &AdvertiseAddrs{
|
||||
RPC: "127.0.0.1",
|
||||
Serf: "127.0.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
c2 := &Config{
|
||||
@@ -55,7 +67,6 @@ func TestConfig_Merge(t *testing.T) {
|
||||
NodeName: "node2",
|
||||
DataDir: "/tmp/dir2",
|
||||
LogLevel: "DEBUG",
|
||||
HttpAddr: "0.0.0.0:80",
|
||||
EnableDebug: true,
|
||||
LeaveOnInt: true,
|
||||
LeaveOnTerm: true,
|
||||
@@ -63,6 +74,7 @@ func TestConfig_Merge(t *testing.T) {
|
||||
SyslogFacility: "local0.debug",
|
||||
DisableUpdateCheck: true,
|
||||
DisableAnonymousSignature: true,
|
||||
BindAddr: "127.0.0.2",
|
||||
Telemetry: &Telemetry{
|
||||
StatsiteAddr: "127.0.0.2:8125",
|
||||
StatsdAddr: "127.0.0.2:8125",
|
||||
@@ -83,11 +95,23 @@ func TestConfig_Merge(t *testing.T) {
|
||||
BootstrapExpect: 2,
|
||||
DataDir: "/tmp/data2",
|
||||
ProtocolVersion: 2,
|
||||
AdvertiseAddr: "127.0.0.2:4647",
|
||||
BindAddr: "127.0.0.2",
|
||||
NumSchedulers: 2,
|
||||
EnabledSchedulers: []string{structs.JobTypeBatch},
|
||||
},
|
||||
Ports: &Ports{
|
||||
HTTP: 20000,
|
||||
RPC: 21000,
|
||||
Serf: 22000,
|
||||
},
|
||||
Addresses: &Addresses{
|
||||
HTTP: "127.0.0.2",
|
||||
RPC: "127.0.0.2",
|
||||
Serf: "127.0.0.2",
|
||||
},
|
||||
AdvertiseAddrs: &AdvertiseAddrs{
|
||||
RPC: "127.0.0.2",
|
||||
Serf: "127.0.0.2",
|
||||
},
|
||||
}
|
||||
|
||||
result := c1.Merge(c2)
|
||||
@@ -231,3 +255,44 @@ func TestConfig_LoadConfig(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Listener(t *testing.T) {
|
||||
config := DefaultConfig()
|
||||
|
||||
// Fails on invalid input
|
||||
if _, err := config.Listener("tcp", "nope", 8080); err == nil {
|
||||
t.Fatalf("expected addr error")
|
||||
}
|
||||
if _, err := config.Listener("nope", "127.0.0.1", 8080); err == nil {
|
||||
t.Fatalf("expected protocol err")
|
||||
}
|
||||
if _, err := config.Listener("tcp", "127.0.0.1", -1); err == nil {
|
||||
t.Fatalf("expected port error")
|
||||
}
|
||||
|
||||
// Works with valid inputs
|
||||
ln, err := config.Listener("tcp", "127.0.0.1", 24000)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
ln.Close()
|
||||
|
||||
if net := ln.Addr().Network(); net != "tcp" {
|
||||
t.Fatalf("expected tcp, got: %q", net)
|
||||
}
|
||||
if addr := ln.Addr().String(); addr != "127.0.0.1:24000" {
|
||||
t.Fatalf("expected 127.0.0.1:4646, got: %q", addr)
|
||||
}
|
||||
|
||||
// Falls back to default bind address if non provided
|
||||
config.BindAddr = "0.0.0.0"
|
||||
ln, err = config.Listener("tcp4", "", 24000)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
ln.Close()
|
||||
|
||||
if addr := ln.Addr().String(); addr != "0.0.0.0:24000" {
|
||||
t.Fatalf("expected 0.0.0.0:24000, got: %q", addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ type HTTPServer struct {
|
||||
// NewHTTPServer starts new HTTP server over the agent
|
||||
func NewHTTPServer(agent *Agent, config *Config, logOutput io.Writer) (*HTTPServer, error) {
|
||||
// Start the listener
|
||||
ln, err := net.Listen("tcp", config.HttpAddr)
|
||||
ln, err := config.Listener("tcp", config.Addresses.HTTP, config.Ports.HTTP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start HTTP listener on %s: %v", config.HttpAddr, err)
|
||||
return nil, fmt.Errorf("failed to start HTTP listener: %v", err)
|
||||
}
|
||||
|
||||
// Create the mux
|
||||
|
||||
@@ -268,7 +268,7 @@ func TestParseRegion(t *testing.T) {
|
||||
}
|
||||
|
||||
s.Server.parseRegion(req, ®ion)
|
||||
if region != "region1" {
|
||||
if region != "global" {
|
||||
t.Fatalf("bad %s", region)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestHTTP_JobsList(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
@@ -62,7 +62,7 @@ func TestHTTP_JobsRegister(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
buf := encodeReq(args)
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestHTTP_JobsRegister(t *testing.T) {
|
||||
// Check the job is registered
|
||||
getReq := structs.JobSpecificRequest{
|
||||
JobID: job.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var getResp structs.SingleJobResponse
|
||||
if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
|
||||
@@ -112,7 +112,7 @@ func TestHTTP_JobQuery(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
@@ -157,7 +157,7 @@ func TestHTTP_JobUpdate(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
buf := encodeReq(args)
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestHTTP_JobUpdate(t *testing.T) {
|
||||
// Check the job is registered
|
||||
getReq := structs.JobSpecificRequest{
|
||||
JobID: job.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var getResp structs.SingleJobResponse
|
||||
if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
|
||||
@@ -207,7 +207,7 @@ func TestHTTP_JobDelete(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
@@ -241,7 +241,7 @@ func TestHTTP_JobDelete(t *testing.T) {
|
||||
// Check the job is gone
|
||||
getReq := structs.JobSpecificRequest{
|
||||
JobID: job.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var getResp structs.SingleJobResponse
|
||||
if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
|
||||
@@ -259,7 +259,7 @@ func TestHTTP_JobForceEvaluate(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
@@ -298,7 +298,7 @@ func TestHTTP_JobEvaluations(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
@@ -343,7 +343,7 @@ func TestHTTP_JobAllocations(t *testing.T) {
|
||||
job := mock.Job()
|
||||
args := structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.JobRegisterResponse
|
||||
if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestHTTP_NodesList(t *testing.T) {
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
|
||||
@@ -62,7 +62,7 @@ func TestHTTP_NodeForceEval(t *testing.T) {
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
|
||||
@@ -110,7 +110,7 @@ func TestHTTP_NodeAllocations(t *testing.T) {
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
|
||||
@@ -164,7 +164,7 @@ func TestHTTP_NodeDrain(t *testing.T) {
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
|
||||
@@ -212,7 +212,7 @@ func TestHTTP_NodeQuery(t *testing.T) {
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestAllocEndpoint_List(t *testing.T) {
|
||||
|
||||
// Lookup the jobs
|
||||
get := &structs.AllocListRequest{
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp structs.AllocListResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp); err != nil {
|
||||
@@ -61,7 +61,7 @@ func TestAllocEndpoint_GetAlloc(t *testing.T) {
|
||||
// Lookup the jobs
|
||||
get := &structs.AllocSpecificRequest{
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp structs.SingleAllocResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp); err != nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultRegion = "region1"
|
||||
DefaultRegion = "global"
|
||||
DefaultDC = "dc1"
|
||||
DefaultSerfPort = 4648
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestEvalEndpoint_GetEval(t *testing.T) {
|
||||
// Lookup the eval
|
||||
get := &structs.EvalSpecificRequest{
|
||||
EvalID: eval1.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp structs.SingleEvalResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.GetEval", get, &resp); err != nil {
|
||||
@@ -71,7 +71,7 @@ func TestEvalEndpoint_Dequeue(t *testing.T) {
|
||||
// Dequeue the eval
|
||||
get := &structs.EvalDequeueRequest{
|
||||
Schedulers: defaultSched,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.EvalDequeueResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Dequeue", get, &resp); err != nil {
|
||||
@@ -118,7 +118,7 @@ func TestEvalEndpoint_Ack(t *testing.T) {
|
||||
get := &structs.EvalAckRequest{
|
||||
EvalID: out.ID,
|
||||
Token: token,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Ack", get, &resp); err != nil {
|
||||
@@ -154,7 +154,7 @@ func TestEvalEndpoint_Nack(t *testing.T) {
|
||||
get := &structs.EvalAckRequest{
|
||||
EvalID: out.ID,
|
||||
Token: token,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Nack", get, &resp); err != nil {
|
||||
@@ -202,7 +202,7 @@ func TestEvalEndpoint_Update(t *testing.T) {
|
||||
get := &structs.EvalUpdateRequest{
|
||||
Evals: []*structs.Evaluation{eval2},
|
||||
EvalToken: token,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Update", get, &resp); err != nil {
|
||||
@@ -247,7 +247,7 @@ func TestEvalEndpoint_Create(t *testing.T) {
|
||||
get := &structs.EvalUpdateRequest{
|
||||
Evals: []*structs.Evaluation{eval1},
|
||||
EvalToken: token,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Create", get, &resp); err != nil {
|
||||
@@ -280,7 +280,7 @@ func TestEvalEndpoint_Reap(t *testing.T) {
|
||||
// Reap the eval
|
||||
get := &structs.EvalDeleteRequest{
|
||||
Evals: []string{eval1.ID},
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Reap", get, &resp); err != nil {
|
||||
@@ -313,7 +313,7 @@ func TestEvalEndpoint_List(t *testing.T) {
|
||||
|
||||
// Lookup the eval
|
||||
get := &structs.EvalListRequest{
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp structs.EvalListResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.List", get, &resp); err != nil {
|
||||
@@ -348,7 +348,7 @@ func TestEvalEndpoint_Allocations(t *testing.T) {
|
||||
// Lookup the eval
|
||||
get := &structs.EvalSpecificRequest{
|
||||
EvalID: alloc1.EvalID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp structs.EvalAllocationsResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Eval.Allocations", get, &resp); err != nil {
|
||||
|
||||
@@ -225,7 +225,7 @@ func TestServer_HeartbeatTTL_Failover(t *testing.T) {
|
||||
node := mock.Node()
|
||||
req := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestJobEndpoint_Register(t *testing.T) {
|
||||
job := mock.Job()
|
||||
req := &structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -87,7 +87,7 @@ func TestJobEndpoint_Register_Existing(t *testing.T) {
|
||||
job := mock.Job()
|
||||
req := &structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -171,7 +171,7 @@ func TestJobEndpoint_Evaluate(t *testing.T) {
|
||||
job := mock.Job()
|
||||
req := &structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -186,7 +186,7 @@ func TestJobEndpoint_Evaluate(t *testing.T) {
|
||||
// Force a re-evaluation
|
||||
reEval := &structs.JobEvaluateRequest{
|
||||
JobID: job.ID,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -240,7 +240,7 @@ func TestJobEndpoint_Deregister(t *testing.T) {
|
||||
job := mock.Job()
|
||||
reg := &structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -252,7 +252,7 @@ func TestJobEndpoint_Deregister(t *testing.T) {
|
||||
// Deregister
|
||||
dereg := &structs.JobDeregisterRequest{
|
||||
JobID: job.ID,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.JobDeregisterResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
|
||||
@@ -314,7 +314,7 @@ func TestJobEndpoint_GetJob(t *testing.T) {
|
||||
job := mock.Job()
|
||||
reg := &structs.JobRegisterRequest{
|
||||
Job: job,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -328,7 +328,7 @@ func TestJobEndpoint_GetJob(t *testing.T) {
|
||||
// Lookup the job
|
||||
get := &structs.JobSpecificRequest{
|
||||
JobID: job.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.SingleJobResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
|
||||
@@ -371,7 +371,7 @@ func TestJobEndpoint_ListJobs(t *testing.T) {
|
||||
|
||||
// Lookup the jobs
|
||||
get := &structs.JobListRequest{
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.JobListResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil {
|
||||
@@ -409,7 +409,7 @@ func TestJobEndpoint_Allocations(t *testing.T) {
|
||||
// Lookup the jobs
|
||||
get := &structs.JobSpecificRequest{
|
||||
JobID: alloc1.JobID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.JobAllocationsResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil {
|
||||
@@ -444,7 +444,7 @@ func TestJobEndpoint_Evaluations(t *testing.T) {
|
||||
// Lookup the jobs
|
||||
get := &structs.JobSpecificRequest{
|
||||
JobID: eval1.JobID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.JobEvaluationsResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil {
|
||||
|
||||
@@ -20,10 +20,9 @@ func Node() *structs.Node {
|
||||
IOPS: 150,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
Public: true,
|
||||
CIDR: "192.168.0.100/32",
|
||||
ReservedPorts: []int{22},
|
||||
MBits: 1000,
|
||||
Device: "eth0",
|
||||
CIDR: "192.168.0.100/32",
|
||||
MBits: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -31,6 +30,14 @@ func Node() *structs.Node {
|
||||
CPU: 0.1,
|
||||
MemoryMB: 256,
|
||||
DiskMB: 4 * 1024,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
Links: map[string]string{
|
||||
"consul": "foobar.dc1",
|
||||
@@ -75,6 +82,12 @@ func Job() *structs.Job {
|
||||
Resources: &structs.Resources{
|
||||
CPU: 0.5,
|
||||
MemoryMB: 256,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -113,16 +126,30 @@ func Alloc() *structs.Allocation {
|
||||
NodeID: "foo",
|
||||
TaskGroup: "web",
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1.0,
|
||||
MemoryMB: 1024,
|
||||
DiskMB: 1024,
|
||||
IOPS: 10,
|
||||
CPU: 0.5,
|
||||
MemoryMB: 256,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
Public: true,
|
||||
CIDR: "192.168.0.100/32",
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{12345},
|
||||
MBits: 100,
|
||||
DynamicPorts: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
TaskResources: map[string]*structs.Resources{
|
||||
"web": &structs.Resources{
|
||||
CPU: 0.5,
|
||||
MemoryMB: 256,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{5000},
|
||||
MBits: 50,
|
||||
DynamicPorts: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestClientEndpoint_Register(t *testing.T) {
|
||||
node := mock.Node()
|
||||
req := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -57,7 +57,7 @@ func TestClientEndpoint_Deregister(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -69,7 +69,7 @@ func TestClientEndpoint_Deregister(t *testing.T) {
|
||||
// Deregister
|
||||
dereg := &structs.NodeDeregisterRequest{
|
||||
NodeID: node.ID,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.GenericResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.Deregister", dereg, &resp2); err != nil {
|
||||
@@ -100,7 +100,7 @@ func TestClientEndpoint_UpdateStatus(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -119,7 +119,7 @@ func TestClientEndpoint_UpdateStatus(t *testing.T) {
|
||||
dereg := &structs.NodeUpdateStatusRequest{
|
||||
NodeID: node.ID,
|
||||
Status: structs.NodeStatusInit,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeUpdateResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.UpdateStatus", dereg, &resp2); err != nil {
|
||||
@@ -159,7 +159,7 @@ func TestClientEndpoint_UpdateStatus_HeartbeatOnly(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -178,7 +178,7 @@ func TestClientEndpoint_UpdateStatus_HeartbeatOnly(t *testing.T) {
|
||||
dereg := &structs.NodeUpdateStatusRequest{
|
||||
NodeID: node.ID,
|
||||
Status: node.Status,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeUpdateResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.UpdateStatus", dereg, &resp2); err != nil {
|
||||
@@ -205,7 +205,7 @@ func TestClientEndpoint_UpdateDrain(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -218,7 +218,7 @@ func TestClientEndpoint_UpdateDrain(t *testing.T) {
|
||||
dereg := &structs.NodeUpdateDrainRequest{
|
||||
NodeID: node.ID,
|
||||
Drain: true,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeDrainUpdateResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.UpdateDrain", dereg, &resp2); err != nil {
|
||||
@@ -249,7 +249,7 @@ func TestClientEndpoint_GetNode(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -263,7 +263,7 @@ func TestClientEndpoint_GetNode(t *testing.T) {
|
||||
// Lookup the node
|
||||
get := &structs.NodeSpecificRequest{
|
||||
NodeID: node.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.SingleNodeResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.GetNode", get, &resp2); err != nil {
|
||||
@@ -300,7 +300,7 @@ func TestClientEndpoint_GetAllocs(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -323,7 +323,7 @@ func TestClientEndpoint_GetAllocs(t *testing.T) {
|
||||
// Lookup the allocs
|
||||
get := &structs.NodeSpecificRequest{
|
||||
NodeID: node.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeAllocsResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.GetAllocs", get, &resp2); err != nil {
|
||||
@@ -360,7 +360,7 @@ func TestClientEndpoint_GetAllocs_Blocking(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -388,7 +388,7 @@ func TestClientEndpoint_GetAllocs_Blocking(t *testing.T) {
|
||||
get := &structs.NodeSpecificRequest{
|
||||
NodeID: node.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
MinQueryIndex: 50,
|
||||
MaxQueryTime: time.Second,
|
||||
},
|
||||
@@ -422,7 +422,7 @@ func TestClientEndpoint_UpdateAlloc(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -448,7 +448,7 @@ func TestClientEndpoint_UpdateAlloc(t *testing.T) {
|
||||
// Update the alloc
|
||||
update := &structs.AllocUpdateRequest{
|
||||
Alloc: []*structs.Allocation{clientAlloc},
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeAllocsResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.UpdateAlloc", update, &resp2); err != nil {
|
||||
@@ -551,7 +551,7 @@ func TestClientEndpoint_Evaluate(t *testing.T) {
|
||||
// Re-evaluate
|
||||
req := &structs.NodeEvaluateRequest{
|
||||
NodeID: alloc.NodeID,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -614,7 +614,7 @@ func TestClientEndpoint_ListNodes(t *testing.T) {
|
||||
node := mock.Node()
|
||||
reg := &structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
@@ -627,7 +627,7 @@ func TestClientEndpoint_ListNodes(t *testing.T) {
|
||||
|
||||
// Lookup the node
|
||||
get := &structs.NodeListRequest{
|
||||
QueryOptions: structs.QueryOptions{Region: "region1"},
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
}
|
||||
var resp2 structs.NodeListResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Node.List", get, &resp2); err != nil {
|
||||
|
||||
@@ -194,6 +194,6 @@ func evaluateNodePlan(snap *state.StateSnapshot, plan *structs.Plan, nodeID stri
|
||||
proposed = append(proposed, plan.NodeAllocation[nodeID]...)
|
||||
|
||||
// Check if these allocations fit
|
||||
fit, _, err := structs.AllocsFit(node, proposed)
|
||||
fit, _, _, err := structs.AllocsFit(node, proposed, nil)
|
||||
return fit, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func testRegisterNode(t *testing.T, s *Server, n *structs.Node) {
|
||||
// Create the register request
|
||||
req := &structs.NodeRegisterRequest{
|
||||
Node: n,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
|
||||
// Fetch the response
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestPlanEndpoint_Submit(t *testing.T) {
|
||||
plan.EvalToken = token
|
||||
req := &structs.PlanRequest{
|
||||
Plan: plan,
|
||||
WriteRequest: structs.WriteRequest{Region: "region1"},
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.PlanResponse
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Plan.Submit", req, &resp); err != nil {
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestRPC_forwardRegion(t *testing.T) {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
err = s2.forwardRegion("region1", "Status.Ping", struct{}{}, &out)
|
||||
err = s2.forwardRegion("global", "Status.Ping", struct{}{}, &out)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestStatusVersion(t *testing.T) {
|
||||
|
||||
arg := &structs.GenericRequest{
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
AllowStale: true,
|
||||
},
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func TestStatusLeader(t *testing.T) {
|
||||
|
||||
arg := &structs.GenericRequest{
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
AllowStale: true,
|
||||
},
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestStatusPeers(t *testing.T) {
|
||||
|
||||
arg := &structs.GenericRequest{
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "region1",
|
||||
Region: "global",
|
||||
AllowStale: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -41,60 +41,49 @@ func FilterTerminalAllocs(allocs []*Allocation) []*Allocation {
|
||||
return allocs[:n]
|
||||
}
|
||||
|
||||
// PortsOvercommited checks if any ports are over-committed.
|
||||
// This does not handle CIDR subsets, and computes for the entire
|
||||
// CIDR block currently.
|
||||
func PortsOvercommited(r *Resources) bool {
|
||||
for _, net := range r.Networks {
|
||||
ports := make(map[int]struct{})
|
||||
for _, port := range net.ReservedPorts {
|
||||
if _, ok := ports[port]; ok {
|
||||
return true
|
||||
}
|
||||
ports[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AllocsFit checks if a given set of allocations will fit on a node
|
||||
func AllocsFit(node *Node, allocs []*Allocation) (bool, *Resources, error) {
|
||||
// AllocsFit checks if a given set of allocations will fit on a node.
|
||||
// The netIdx can optionally be provided if its already been computed.
|
||||
// If the netIdx is provided, it is assumed that the client has already
|
||||
// ensured there are no collisions.
|
||||
func AllocsFit(node *Node, allocs []*Allocation, netIdx *NetworkIndex) (bool, string, *Resources, error) {
|
||||
// Compute the utilization from zero
|
||||
used := new(Resources)
|
||||
for _, net := range node.Resources.Networks {
|
||||
used.Networks = append(used.Networks, &NetworkResource{
|
||||
Public: net.Public,
|
||||
CIDR: net.CIDR,
|
||||
})
|
||||
}
|
||||
|
||||
// Add the reserved resources of the node
|
||||
if node.Reserved != nil {
|
||||
if err := used.Add(node.Reserved); err != nil {
|
||||
return false, nil, err
|
||||
return false, "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// For each alloc, add the resources
|
||||
for _, alloc := range allocs {
|
||||
if err := used.Add(alloc.Resources); err != nil {
|
||||
return false, nil, err
|
||||
return false, "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the node resources are a super set of those
|
||||
// that are being allocated
|
||||
if !node.Resources.Superset(used) {
|
||||
return false, used, nil
|
||||
if superset, dimension := node.Resources.Superset(used); !superset {
|
||||
return false, dimension, used, nil
|
||||
}
|
||||
|
||||
// Ensure ports are not over commited
|
||||
if PortsOvercommited(used) {
|
||||
return false, used, nil
|
||||
// Create the network index if missing
|
||||
if netIdx == nil {
|
||||
netIdx = NewNetworkIndex()
|
||||
if netIdx.SetNode(node) || netIdx.AddAllocs(allocs) {
|
||||
return false, "reserved port collision", used, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the network is overcommitted
|
||||
if netIdx.Overcommitted() {
|
||||
return false, "bandwidth exceeded", used, nil
|
||||
}
|
||||
|
||||
// Allocations fit!
|
||||
return true, used, nil
|
||||
return true, "", used, nil
|
||||
}
|
||||
|
||||
// ScoreFit is used to score the fit based on the Google work published here:
|
||||
|
||||
@@ -39,25 +39,48 @@ func TestFilterTerminalALlocs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortsOvercommitted(t *testing.T) {
|
||||
r := &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
ReservedPorts: []int{22, 80},
|
||||
},
|
||||
&NetworkResource{
|
||||
ReservedPorts: []int{22, 80},
|
||||
func TestAllocsFit_PortsOvercommitted(t *testing.T) {
|
||||
n := &Node{
|
||||
Resources: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
MBits: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if PortsOvercommited(r) {
|
||||
t.Fatalf("bad")
|
||||
|
||||
a1 := &Allocation{
|
||||
TaskResources: map[string]*Resources{
|
||||
"web": &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{8000},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Overcommit 22
|
||||
r.Networks[1].ReservedPorts[1] = 22
|
||||
if !PortsOvercommited(r) {
|
||||
t.Fatalf("bad")
|
||||
// Should fit one allocation
|
||||
fit, _, _, err := AllocsFit(n, []*Allocation{a1}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !fit {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
|
||||
// Should not fit second allocation
|
||||
fit, _, _, err = AllocsFit(n, []*Allocation{a1, a1}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if fit {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +105,7 @@ func TestAllocsFit(t *testing.T) {
|
||||
IOPS: 50,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{80},
|
||||
},
|
||||
@@ -98,7 +121,7 @@ func TestAllocsFit(t *testing.T) {
|
||||
IOPS: 50,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{8000},
|
||||
},
|
||||
@@ -107,7 +130,7 @@ func TestAllocsFit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Should fit one allocation
|
||||
fit, used, err := AllocsFit(n, []*Allocation{a1})
|
||||
fit, _, used, err := AllocsFit(n, []*Allocation{a1}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@@ -124,7 +147,7 @@ func TestAllocsFit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Should not fit second allocation
|
||||
fit, used, err = AllocsFit(n, []*Allocation{a1, a1})
|
||||
fit, _, used, err = AllocsFit(n, []*Allocation{a1, a1}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
202
nomad/structs/network.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
// MinDynamicPort is the smallest dynamic port generated
|
||||
MinDynamicPort = 20000
|
||||
|
||||
// MaxDynamicPort is the largest dynamic port generated
|
||||
MaxDynamicPort = 60000
|
||||
|
||||
// maxRandPortAttempts is the maximum number of attempt
|
||||
// to assign a random port
|
||||
maxRandPortAttempts = 20
|
||||
)
|
||||
|
||||
// NetworkIndex is used to index the available network resources
|
||||
// and the used network resources on a machine given allocations
|
||||
type NetworkIndex struct {
|
||||
AvailNetworks []*NetworkResource // List of available networks
|
||||
AvailBandwidth map[string]int // Bandwidth by device
|
||||
UsedPorts map[string]map[int]struct{} // Ports by IP
|
||||
UsedBandwidth map[string]int // Bandwidth by device
|
||||
}
|
||||
|
||||
// NewNetworkIndex is used to construct a new network index
|
||||
func NewNetworkIndex() *NetworkIndex {
|
||||
return &NetworkIndex{
|
||||
AvailBandwidth: make(map[string]int),
|
||||
UsedPorts: make(map[string]map[int]struct{}),
|
||||
UsedBandwidth: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// Overcommitted checks if the network is overcommitted
|
||||
func (idx *NetworkIndex) Overcommitted() bool {
|
||||
for device, used := range idx.UsedBandwidth {
|
||||
avail := idx.AvailBandwidth[device]
|
||||
if used > avail {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetNode is used to setup the available network resources. Returns
|
||||
// true if there is a collision
|
||||
func (idx *NetworkIndex) SetNode(node *Node) (collide bool) {
|
||||
// Add the available CIDR blocks
|
||||
for _, n := range node.Resources.Networks {
|
||||
if n.Device != "" {
|
||||
idx.AvailNetworks = append(idx.AvailNetworks, n)
|
||||
idx.AvailBandwidth[n.Device] = n.MBits
|
||||
}
|
||||
}
|
||||
|
||||
// Add the reserved resources
|
||||
if r := node.Reserved; r != nil {
|
||||
for _, n := range r.Networks {
|
||||
if idx.AddReserved(n) {
|
||||
collide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddAllocs is used to add the used network resources. Returns
|
||||
// true if there is a collision
|
||||
func (idx *NetworkIndex) AddAllocs(allocs []*Allocation) (collide bool) {
|
||||
for _, alloc := range allocs {
|
||||
for _, task := range alloc.TaskResources {
|
||||
if len(task.Networks) == 0 {
|
||||
continue
|
||||
}
|
||||
n := task.Networks[0]
|
||||
if idx.AddReserved(n) {
|
||||
collide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddReserved is used to add a reserved network usage, returns true
|
||||
// if there is a port collision
|
||||
func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
|
||||
// Add the port usage
|
||||
used := idx.UsedPorts[n.IP]
|
||||
if used == nil {
|
||||
used = make(map[int]struct{})
|
||||
idx.UsedPorts[n.IP] = used
|
||||
}
|
||||
for _, port := range n.ReservedPorts {
|
||||
if _, ok := used[port]; ok {
|
||||
collide = true
|
||||
} else {
|
||||
used[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the bandwidth
|
||||
idx.UsedBandwidth[n.Device] += n.MBits
|
||||
return
|
||||
}
|
||||
|
||||
// yieldIP is used to iteratively invoke the callback with
|
||||
// an available IP
|
||||
func (idx *NetworkIndex) yieldIP(cb func(net *NetworkResource, ip net.IP) bool) {
|
||||
inc := func(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range idx.AvailNetworks {
|
||||
ip, ipnet, err := net.ParseCIDR(n.CIDR)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
|
||||
if cb(n, ip) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AssignNetwork is used to assign network resources given an ask.
|
||||
// If the ask cannot be satisfied, returns nil
|
||||
func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResource, err error) {
|
||||
idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
|
||||
// Convert the IP to a string
|
||||
ipStr := ip.String()
|
||||
|
||||
// Check if we would exceed the bandwidth cap
|
||||
availBandwidth := idx.AvailBandwidth[n.Device]
|
||||
usedBandwidth := idx.UsedBandwidth[n.Device]
|
||||
if usedBandwidth+ask.MBits > availBandwidth {
|
||||
err = fmt.Errorf("bandwidth exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if any of the reserved ports are in use
|
||||
for _, port := range ask.ReservedPorts {
|
||||
if _, ok := idx.UsedPorts[ipStr][port]; ok {
|
||||
err = fmt.Errorf("reserved port collision")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create the offer
|
||||
offer := &NetworkResource{
|
||||
Device: n.Device,
|
||||
IP: ipStr,
|
||||
ReservedPorts: ask.ReservedPorts,
|
||||
}
|
||||
|
||||
// Check if we need to generate any ports
|
||||
for i := 0; i < ask.DynamicPorts; i++ {
|
||||
attempts := 0
|
||||
PICK:
|
||||
attempts++
|
||||
if attempts > maxRandPortAttempts {
|
||||
err = fmt.Errorf("dynamic port selection failed")
|
||||
return
|
||||
}
|
||||
|
||||
randPort := MinDynamicPort + rand.Intn(MaxDynamicPort-MinDynamicPort)
|
||||
if _, ok := idx.UsedPorts[ipStr][randPort]; ok {
|
||||
goto PICK
|
||||
}
|
||||
if IntContains(offer.ReservedPorts, randPort) {
|
||||
goto PICK
|
||||
}
|
||||
offer.ReservedPorts = append(offer.ReservedPorts, randPort)
|
||||
}
|
||||
|
||||
// Stop, we have an offer!
|
||||
out = offer
|
||||
err = nil
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// IntContains scans an integer slice for a value
|
||||
func IntContains(haystack []int, needle int) bool {
|
||||
for _, item := range haystack {
|
||||
if item == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
349
nomad/structs/network_test.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNetworkIndex_Overcommitted(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
|
||||
// Consume some network
|
||||
reserved := &NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 505,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
}
|
||||
collide := idx.AddReserved(reserved)
|
||||
if collide {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !idx.Overcommitted() {
|
||||
t.Fatalf("have no resources")
|
||||
}
|
||||
|
||||
// Add resources
|
||||
n := &Node{
|
||||
Resources: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
CIDR: "192.168.0.100/32",
|
||||
MBits: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
idx.SetNode(n)
|
||||
if idx.Overcommitted() {
|
||||
t.Fatalf("have resources")
|
||||
}
|
||||
|
||||
// Double up our ussage
|
||||
idx.AddReserved(reserved)
|
||||
if !idx.Overcommitted() {
|
||||
t.Fatalf("should be overcommitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkIndex_SetNode(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
n := &Node{
|
||||
Resources: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
CIDR: "192.168.0.100/32",
|
||||
MBits: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Reserved: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
collide := idx.SetNode(n)
|
||||
if collide {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
|
||||
if len(idx.AvailNetworks) != 1 {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if idx.AvailBandwidth["eth0"] != 1000 {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if idx.UsedBandwidth["eth0"] != 1 {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][22]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkIndex_AddAllocs(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
allocs := []*Allocation{
|
||||
&Allocation{
|
||||
TaskResources: map[string]*Resources{
|
||||
"web": &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&Allocation{
|
||||
TaskResources: map[string]*Resources{
|
||||
"api": &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{10000},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
collide := idx.AddAllocs(allocs)
|
||||
if collide {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
|
||||
if idx.UsedBandwidth["eth0"] != 70 {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][8000]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][9000]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][10000]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkIndex_AddReserved(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
|
||||
reserved := &NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
}
|
||||
collide := idx.AddReserved(reserved)
|
||||
if collide {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
|
||||
if idx.UsedBandwidth["eth0"] != 20 {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][8000]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
if _, ok := idx.UsedPorts["192.168.0.100"][9000]; !ok {
|
||||
t.Fatalf("Bad")
|
||||
}
|
||||
|
||||
// Try to reserve the same network
|
||||
collide = idx.AddReserved(reserved)
|
||||
if !collide {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkIndex_yieldIP(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
n := &Node{
|
||||
Resources: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
CIDR: "192.168.0.100/30",
|
||||
MBits: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Reserved: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
idx.SetNode(n)
|
||||
|
||||
var out []string
|
||||
idx.yieldIP(func(n *NetworkResource, ip net.IP) (stop bool) {
|
||||
out = append(out, ip.String())
|
||||
return
|
||||
})
|
||||
|
||||
expect := []string{"192.168.0.100", "192.168.0.101",
|
||||
"192.168.0.102", "192.168.0.103"}
|
||||
if !reflect.DeepEqual(out, expect) {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
||||
idx := NewNetworkIndex()
|
||||
n := &Node{
|
||||
Resources: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
CIDR: "192.168.0.100/30",
|
||||
MBits: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Reserved: &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
idx.SetNode(n)
|
||||
|
||||
allocs := []*Allocation{
|
||||
&Allocation{
|
||||
TaskResources: map[string]*Resources{
|
||||
"web": &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&Allocation{
|
||||
TaskResources: map[string]*Resources{
|
||||
"api": &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{10000},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
idx.AddAllocs(allocs)
|
||||
|
||||
// Ask for a reserved port
|
||||
ask := &NetworkResource{
|
||||
ReservedPorts: []int{8000},
|
||||
}
|
||||
offer, err := idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if offer == nil {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if offer.IP != "192.168.0.101" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != 8000 {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
|
||||
// Ask for dynamic ports
|
||||
ask = &NetworkResource{
|
||||
DynamicPorts: 3,
|
||||
}
|
||||
offer, err = idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if offer == nil {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if offer.IP != "192.168.0.100" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 3 {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
|
||||
// Ask for reserved + dynamic ports
|
||||
ask = &NetworkResource{
|
||||
ReservedPorts: []int{12345},
|
||||
DynamicPorts: 3,
|
||||
}
|
||||
offer, err = idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if offer == nil {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if offer.IP != "192.168.0.100" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 4 || offer.ReservedPorts[0] != 12345 {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
|
||||
// Ask for too much bandwidth
|
||||
ask = &NetworkResource{
|
||||
MBits: 1000,
|
||||
}
|
||||
offer, err = idx.AssignNetwork(ask)
|
||||
if err.Error() != "bandwidth exceeded" {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if offer != nil {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntContains(t *testing.T) {
|
||||
l := []int{1, 2, 10, 20}
|
||||
if IntContains(l, 50) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !IntContains(l, 20) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !IntContains(l, 1) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
@@ -538,12 +538,22 @@ type Resources struct {
|
||||
Networks []*NetworkResource
|
||||
}
|
||||
|
||||
// NetIndexByCIDR scans the list of networks for a matching
|
||||
// CIDR, returning the index. This currently ONLY handles
|
||||
// an exact match and not a subset CIDR.
|
||||
func (r *Resources) NetIndexByCIDR(cidr string) int {
|
||||
// Copy returns a deep copy of the resources
|
||||
func (r *Resources) Copy() *Resources {
|
||||
newR := new(Resources)
|
||||
*newR = *r
|
||||
n := len(r.Networks)
|
||||
newR.Networks = make([]*NetworkResource, n)
|
||||
for i := 0; i < n; i++ {
|
||||
newR.Networks[i] = r.Networks[i].Copy()
|
||||
}
|
||||
return newR
|
||||
}
|
||||
|
||||
// NetIndex finds the matching net index using device name
|
||||
func (r *Resources) NetIndex(n *NetworkResource) int {
|
||||
for idx, net := range r.Networks {
|
||||
if net.CIDR == cidr {
|
||||
if net.Device == n.Device {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
@@ -551,36 +561,22 @@ func (r *Resources) NetIndexByCIDR(cidr string) int {
|
||||
}
|
||||
|
||||
// Superset checks if one set of resources is a superset
|
||||
// of another.
|
||||
func (r *Resources) Superset(other *Resources) bool {
|
||||
// of another. This ignores network resources, and the NetworkIndex
|
||||
// should be used for that.
|
||||
func (r *Resources) Superset(other *Resources) (bool, string) {
|
||||
if r.CPU < other.CPU {
|
||||
return false
|
||||
return false, "cpu exhausted"
|
||||
}
|
||||
if r.MemoryMB < other.MemoryMB {
|
||||
return false
|
||||
return false, "memory exhausted"
|
||||
}
|
||||
if r.DiskMB < other.DiskMB {
|
||||
return false
|
||||
return false, "disk exhausted"
|
||||
}
|
||||
if r.IOPS < other.IOPS {
|
||||
return false
|
||||
return false, "iops exhausted"
|
||||
}
|
||||
for _, net := range r.Networks {
|
||||
idx := other.NetIndexByCIDR(net.CIDR)
|
||||
if idx >= 0 {
|
||||
if net.MBits < other.Networks[idx].MBits {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that other does not have a network we are missing
|
||||
for _, net := range other.Networks {
|
||||
idx := r.NetIndexByCIDR(net.CIDR)
|
||||
if idx == -1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// Add adds the resources of the delta to this, potentially
|
||||
@@ -594,12 +590,14 @@ func (r *Resources) Add(delta *Resources) error {
|
||||
r.DiskMB += delta.DiskMB
|
||||
r.IOPS += delta.IOPS
|
||||
|
||||
for _, net := range delta.Networks {
|
||||
idx := r.NetIndexByCIDR(net.CIDR)
|
||||
for _, n := range delta.Networks {
|
||||
// Find the matching interface by IP or CIDR
|
||||
idx := r.NetIndex(n)
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("missing network for CIDR %s", net.CIDR)
|
||||
r.Networks = append(r.Networks, n.Copy())
|
||||
} else {
|
||||
r.Networks[idx].Add(n)
|
||||
}
|
||||
r.Networks[idx].Add(net)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -607,10 +605,23 @@ func (r *Resources) Add(delta *Resources) error {
|
||||
// NetworkResource is used to represesent available network
|
||||
// resources
|
||||
type NetworkResource struct {
|
||||
Public bool // Is this a public address?
|
||||
Device string // Name of the device
|
||||
CIDR string // CIDR block of addresses
|
||||
IP string // IP address
|
||||
ReservedPorts []int // Reserved ports
|
||||
MBits int // Throughput
|
||||
DynamicPorts int // Dynamically assigned ports
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the network resource
|
||||
func (n *NetworkResource) Copy() *NetworkResource {
|
||||
newR := new(NetworkResource)
|
||||
*newR = *n
|
||||
if n.ReservedPorts != nil {
|
||||
newR.ReservedPorts = make([]int, len(n.ReservedPorts))
|
||||
copy(newR.ReservedPorts, n.ReservedPorts)
|
||||
}
|
||||
return newR
|
||||
}
|
||||
|
||||
// Add adds the resources of the delta to this, potentially
|
||||
@@ -620,13 +631,13 @@ func (n *NetworkResource) Add(delta *NetworkResource) {
|
||||
n.ReservedPorts = append(n.ReservedPorts, delta.ReservedPorts...)
|
||||
}
|
||||
n.MBits += delta.MBits
|
||||
n.DynamicPorts += delta.DynamicPorts
|
||||
}
|
||||
|
||||
const (
|
||||
// JobTypeNomad is reserved for internal system tasks and is
|
||||
// always handled by the CoreScheduler.
|
||||
JobTypeCore = "_core"
|
||||
JobTypeSystem = "system"
|
||||
JobTypeService = "service"
|
||||
JobTypeBatch = "batch"
|
||||
)
|
||||
@@ -871,10 +882,14 @@ type Allocation struct {
|
||||
// TaskGroup is the name of the task group that should be run
|
||||
TaskGroup string
|
||||
|
||||
// Resources is the set of resources allocated as part
|
||||
// Resources is the total set of resources allocated as part
|
||||
// of this allocation of the task group.
|
||||
Resources *Resources
|
||||
|
||||
// TaskResources is the set of resources allocated to each
|
||||
// task. These should sum to the total Resources.
|
||||
TaskResources map[string]*Resources
|
||||
|
||||
// Metrics associated with this allocation
|
||||
Metrics *AllocMetric
|
||||
|
||||
@@ -964,6 +979,9 @@ type AllocMetric struct {
|
||||
// ClassExhausted is the number of nodes exhausted by class
|
||||
ClassExhausted map[string]int
|
||||
|
||||
// DimensionExhaused provides the count by dimension or reason
|
||||
DimensionExhaused map[string]int
|
||||
|
||||
// Scores is the scores of the final few nodes remaining
|
||||
// for placement. The top score is typically selected.
|
||||
Scores map[string]float64
|
||||
@@ -999,7 +1017,7 @@ func (a *AllocMetric) FilterNode(node *Node, constraint string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AllocMetric) ExhaustedNode(node *Node) {
|
||||
func (a *AllocMetric) ExhaustedNode(node *Node, dimension string) {
|
||||
a.NodesExhausted += 1
|
||||
if node != nil && node.NodeClass != "" {
|
||||
if a.ClassExhausted == nil {
|
||||
@@ -1007,6 +1025,12 @@ func (a *AllocMetric) ExhaustedNode(node *Node) {
|
||||
}
|
||||
a.ClassExhausted[node.NodeClass] += 1
|
||||
}
|
||||
if dimension != "" {
|
||||
if a.DimensionExhaused == nil {
|
||||
a.DimensionExhaused = make(map[string]int)
|
||||
}
|
||||
a.DimensionExhaused[dimension] += 1
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AllocMetric) ScoreNode(node *Node, name string, score float64) {
|
||||
|
||||
@@ -5,20 +5,21 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResource_NetIndexByCIDR(t *testing.T) {
|
||||
func TestResource_NetIndex(t *testing.T) {
|
||||
r := &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{CIDR: "10.0.0.0/8"},
|
||||
&NetworkResource{CIDR: "127.0.0.0/24"},
|
||||
&NetworkResource{Device: "eth0"},
|
||||
&NetworkResource{Device: "lo0"},
|
||||
&NetworkResource{Device: ""},
|
||||
},
|
||||
}
|
||||
if idx := r.NetIndexByCIDR("10.0.0.0/8"); idx != 0 {
|
||||
if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
|
||||
t.Fatalf("Bad: %d", idx)
|
||||
}
|
||||
if idx := r.NetIndexByCIDR("127.0.0.0/24"); idx != 1 {
|
||||
if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
|
||||
t.Fatalf("Bad: %d", idx)
|
||||
}
|
||||
if idx := r.NetIndexByCIDR("10.0.0.0/16"); idx != -1 {
|
||||
if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
|
||||
t.Fatalf("Bad: %d", idx)
|
||||
}
|
||||
}
|
||||
@@ -29,36 +30,24 @@ func TestResource_Superset(t *testing.T) {
|
||||
MemoryMB: 2048,
|
||||
DiskMB: 10000,
|
||||
IOPS: 100,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
MBits: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
r2 := &Resources{
|
||||
CPU: 1.0,
|
||||
MemoryMB: 1024,
|
||||
DiskMB: 5000,
|
||||
IOPS: 50,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
MBits: 50,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !r1.Superset(r1) {
|
||||
if s, _ := r1.Superset(r1); !s {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !r1.Superset(r2) {
|
||||
if s, _ := r1.Superset(r2); !s {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if r2.Superset(r1) {
|
||||
if s, _ := r2.Superset(r1); s {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !r2.Superset(r2) {
|
||||
if s, _ := r2.Superset(r2); !s {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
@@ -84,7 +73,7 @@ func TestResource_Add(t *testing.T) {
|
||||
IOPS: 50,
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{80},
|
||||
},
|
||||
@@ -115,6 +104,48 @@ func TestResource_Add(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_Add_Network(t *testing.T) {
|
||||
r1 := &Resources{}
|
||||
r2 := &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
r3 := &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 25,
|
||||
DynamicPorts: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := r1.Add(r2)
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
err = r1.Add(r3)
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %v", err)
|
||||
}
|
||||
|
||||
expect := &Resources{
|
||||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 75,
|
||||
DynamicPorts: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expect.Networks, r1.Networks) {
|
||||
t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
type FooRequest struct {
|
||||
Foo string
|
||||
|
||||
@@ -311,6 +311,15 @@ func (s *GenericScheduler) inplaceUpdate(updates []allocTuple) []allocTuple {
|
||||
continue
|
||||
}
|
||||
|
||||
// Restore the network offers from the existing allocation.
|
||||
// We do not allow network resources (reserved/dynamic ports)
|
||||
// to be updated. This is guarded in taskUpdated, so we can
|
||||
// safely restore those here.
|
||||
for task, resources := range option.TaskResources {
|
||||
existing := update.Alloc.TaskResources[task]
|
||||
resources.Networks = existing.Networks
|
||||
}
|
||||
|
||||
// Create a shallow copy
|
||||
newAlloc := new(structs.Allocation)
|
||||
*newAlloc = *update.Alloc
|
||||
@@ -319,6 +328,7 @@ func (s *GenericScheduler) inplaceUpdate(updates []allocTuple) []allocTuple {
|
||||
newAlloc.EvalID = s.eval.ID
|
||||
newAlloc.Job = s.job
|
||||
newAlloc.Resources = size
|
||||
newAlloc.TaskResources = option.TaskResources
|
||||
newAlloc.Metrics = s.ctx.Metrics()
|
||||
newAlloc.DesiredStatus = structs.AllocDesiredStatusRun
|
||||
newAlloc.ClientStatus = structs.AllocClientStatusPending
|
||||
@@ -361,36 +371,29 @@ func (s *GenericScheduler) computePlacements(place []allocTuple) error {
|
||||
// Attempt to match the task group
|
||||
option, size := s.stack.Select(missing.TaskGroup)
|
||||
|
||||
// Handle a placement failure
|
||||
var nodeID, status, desc, clientStatus string
|
||||
if option == nil {
|
||||
status = structs.AllocDesiredStatusFailed
|
||||
desc = "failed to find a node for placement"
|
||||
clientStatus = structs.AllocClientStatusFailed
|
||||
} else {
|
||||
nodeID = option.Node.ID
|
||||
status = structs.AllocDesiredStatusRun
|
||||
clientStatus = structs.AllocClientStatusPending
|
||||
}
|
||||
|
||||
// Create an allocation for this
|
||||
alloc := &structs.Allocation{
|
||||
ID: structs.GenerateUUID(),
|
||||
EvalID: s.eval.ID,
|
||||
Name: missing.Name,
|
||||
NodeID: nodeID,
|
||||
JobID: s.job.ID,
|
||||
Job: s.job,
|
||||
TaskGroup: missing.TaskGroup.Name,
|
||||
Resources: size,
|
||||
Metrics: s.ctx.Metrics(),
|
||||
DesiredStatus: status,
|
||||
DesiredDescription: desc,
|
||||
ClientStatus: clientStatus,
|
||||
ID: structs.GenerateUUID(),
|
||||
EvalID: s.eval.ID,
|
||||
Name: missing.Name,
|
||||
JobID: s.job.ID,
|
||||
Job: s.job,
|
||||
TaskGroup: missing.TaskGroup.Name,
|
||||
Resources: size,
|
||||
Metrics: s.ctx.Metrics(),
|
||||
}
|
||||
if nodeID != "" {
|
||||
|
||||
// Set fields based on if we found an allocation option
|
||||
if option != nil {
|
||||
alloc.NodeID = option.Node.ID
|
||||
alloc.TaskResources = option.TaskResources
|
||||
alloc.DesiredStatus = structs.AllocDesiredStatusRun
|
||||
alloc.ClientStatus = structs.AllocClientStatusPending
|
||||
s.plan.AppendAlloc(alloc)
|
||||
} else {
|
||||
alloc.DesiredStatus = structs.AllocDesiredStatusFailed
|
||||
alloc.DesiredDescription = "failed to find a node for placement"
|
||||
alloc.ClientStatus = structs.AllocClientStatusFailed
|
||||
s.plan.AppendFailed(alloc)
|
||||
failedTG[missing.TaskGroup] = alloc
|
||||
}
|
||||
|
||||
@@ -382,6 +382,15 @@ func TestServiceSched_JobModify_InPlace(t *testing.T) {
|
||||
t.Fatalf("bad: %#v", out)
|
||||
}
|
||||
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
||||
|
||||
// Verify the network did not change
|
||||
for _, alloc := range out {
|
||||
for _, resources := range alloc.TaskResources {
|
||||
if resources.Networks[0].ReservedPorts[0] != 5000 {
|
||||
t.Fatalf("bad: %#v", alloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceSched_JobDeregister(t *testing.T) {
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
// along with a node when iterating. This state can be modified as
|
||||
// various rank methods are applied.
|
||||
type RankedNode struct {
|
||||
Node *structs.Node
|
||||
Score float64
|
||||
Node *structs.Node
|
||||
Score float64
|
||||
TaskResources map[string]*structs.Resources
|
||||
|
||||
// Allocs is used to cache the proposed allocations on the
|
||||
// node. This can be shared between iterators that require it.
|
||||
@@ -22,6 +23,27 @@ func (r *RankedNode) GoString() string {
|
||||
return fmt.Sprintf("<Node: %s Score: %0.3f>", r.Node.ID, r.Score)
|
||||
}
|
||||
|
||||
func (r *RankedNode) ProposedAllocs(ctx Context) ([]*structs.Allocation, error) {
|
||||
if r.Proposed != nil {
|
||||
return r.Proposed, nil
|
||||
}
|
||||
|
||||
p, err := ctx.ProposedAllocs(r.Node.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Proposed = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (r *RankedNode) SetTaskResources(task *structs.Task,
|
||||
resource *structs.Resources) {
|
||||
if r.TaskResources == nil {
|
||||
r.TaskResources = make(map[string]*structs.Resources)
|
||||
}
|
||||
r.TaskResources[task.Name] = resource
|
||||
}
|
||||
|
||||
// RankFeasibleIterator is used to iteratively yield nodes along
|
||||
// with ranking metadata. The iterators may manage some state for
|
||||
// performance optimizations.
|
||||
@@ -109,65 +131,92 @@ func (iter *StaticRankIterator) Reset() {
|
||||
// BinPackIterator is a RankIterator that scores potential options
|
||||
// based on a bin-packing algorithm.
|
||||
type BinPackIterator struct {
|
||||
ctx Context
|
||||
source RankIterator
|
||||
resources *structs.Resources
|
||||
evict bool
|
||||
priority int
|
||||
ctx Context
|
||||
source RankIterator
|
||||
evict bool
|
||||
priority int
|
||||
tasks []*structs.Task
|
||||
}
|
||||
|
||||
// NewBinPackIterator returns a BinPackIterator which tries to fit the given
|
||||
// resources, potentially evicting other tasks based on a given priority.
|
||||
func NewBinPackIterator(ctx Context, source RankIterator, resources *structs.Resources, evict bool, priority int) *BinPackIterator {
|
||||
// NewBinPackIterator returns a BinPackIterator which tries to fit tasks
|
||||
// potentially evicting other tasks based on a given priority.
|
||||
func NewBinPackIterator(ctx Context, source RankIterator, evict bool, priority int) *BinPackIterator {
|
||||
iter := &BinPackIterator{
|
||||
ctx: ctx,
|
||||
source: source,
|
||||
resources: resources,
|
||||
evict: evict,
|
||||
priority: priority,
|
||||
ctx: ctx,
|
||||
source: source,
|
||||
evict: evict,
|
||||
priority: priority,
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
func (iter *BinPackIterator) SetResources(r *structs.Resources) {
|
||||
iter.resources = r
|
||||
}
|
||||
|
||||
func (iter *BinPackIterator) SetPriority(p int) {
|
||||
iter.priority = p
|
||||
}
|
||||
|
||||
func (iter *BinPackIterator) SetTasks(tasks []*structs.Task) {
|
||||
iter.tasks = tasks
|
||||
}
|
||||
|
||||
func (iter *BinPackIterator) Next() *RankedNode {
|
||||
OUTER:
|
||||
for {
|
||||
// Get the next potential option
|
||||
option := iter.source.Next()
|
||||
if option == nil {
|
||||
return nil
|
||||
}
|
||||
nodeID := option.Node.ID
|
||||
|
||||
// Get the proposed allocations
|
||||
var proposed []*structs.Allocation
|
||||
if option.Proposed != nil {
|
||||
proposed = option.Proposed
|
||||
} else {
|
||||
p, err := iter.ctx.ProposedAllocs(nodeID)
|
||||
if err != nil {
|
||||
iter.ctx.Logger().Printf("[ERR] sched.binpack: failed to get proposed allocations for '%s': %v",
|
||||
nodeID, err)
|
||||
continue
|
||||
proposed, err := option.ProposedAllocs(iter.ctx)
|
||||
if err != nil {
|
||||
iter.ctx.Logger().Printf(
|
||||
"[ERR] sched.binpack: failed to get proposed allocations: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Index the existing network usage
|
||||
netIdx := structs.NewNetworkIndex()
|
||||
netIdx.SetNode(option.Node)
|
||||
netIdx.AddAllocs(proposed)
|
||||
|
||||
// Assign the resources for each task
|
||||
total := new(structs.Resources)
|
||||
for _, task := range iter.tasks {
|
||||
taskResources := task.Resources.Copy()
|
||||
|
||||
// Check if we need a network resource
|
||||
if len(taskResources.Networks) > 0 {
|
||||
ask := taskResources.Networks[0]
|
||||
offer, err := netIdx.AssignNetwork(ask)
|
||||
if offer == nil {
|
||||
iter.ctx.Metrics().ExhaustedNode(option.Node,
|
||||
fmt.Sprintf("network: %s", err))
|
||||
continue OUTER
|
||||
}
|
||||
|
||||
// Reserve this to prevent another task from colliding
|
||||
netIdx.AddReserved(offer)
|
||||
|
||||
// Update the network ask to the offer
|
||||
taskResources.Networks = []*structs.NetworkResource{offer}
|
||||
}
|
||||
proposed = p
|
||||
option.Proposed = p
|
||||
|
||||
// Store the task resource
|
||||
option.SetTaskResources(task, taskResources)
|
||||
|
||||
// Accumulate the total resource requirement
|
||||
total.Add(taskResources)
|
||||
}
|
||||
|
||||
// Add the resources we are trying to fit
|
||||
proposed = append(proposed, &structs.Allocation{Resources: iter.resources})
|
||||
proposed = append(proposed, &structs.Allocation{Resources: total})
|
||||
|
||||
// Check if these allocations fit, if they do not, simply skip this node
|
||||
fit, util, _ := structs.AllocsFit(option.Node, proposed)
|
||||
fit, dim, util, _ := structs.AllocsFit(option.Node, proposed, netIdx)
|
||||
if !fit {
|
||||
iter.ctx.Metrics().ExhaustedNode(option.Node)
|
||||
iter.ctx.Metrics().ExhaustedNode(option.Node, dim)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -220,21 +269,14 @@ func (iter *JobAntiAffinityIterator) Next() *RankedNode {
|
||||
if option == nil {
|
||||
return nil
|
||||
}
|
||||
nodeID := option.Node.ID
|
||||
|
||||
// Get the proposed allocations
|
||||
var proposed []*structs.Allocation
|
||||
if option.Proposed != nil {
|
||||
proposed = option.Proposed
|
||||
} else {
|
||||
p, err := iter.ctx.ProposedAllocs(nodeID)
|
||||
if err != nil {
|
||||
iter.ctx.Logger().Printf("[ERR] sched.job-anti-affinity: failed to get proposed allocations for '%s': %v",
|
||||
nodeID, err)
|
||||
continue
|
||||
}
|
||||
proposed = p
|
||||
option.Proposed = p
|
||||
proposed, err := option.ProposedAllocs(iter.ctx)
|
||||
if err != nil {
|
||||
iter.ctx.Logger().Printf(
|
||||
"[ERR] sched.job-anti-aff: failed to get proposed allocations: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine the number of collisions
|
||||
|
||||
@@ -68,11 +68,16 @@ func TestBinPackIterator_NoExistingAlloc(t *testing.T) {
|
||||
}
|
||||
static := NewStaticRankIterator(ctx, nodes)
|
||||
|
||||
resources := &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
task := &structs.Task{
|
||||
Name: "web",
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
},
|
||||
}
|
||||
binp := NewBinPackIterator(ctx, static, resources, false, 0)
|
||||
|
||||
binp := NewBinPackIterator(ctx, static, false, 0)
|
||||
binp.SetTasks([]*structs.Task{task})
|
||||
|
||||
out := collectRanked(binp)
|
||||
if len(out) != 2 {
|
||||
@@ -137,11 +142,16 @@ func TestBinPackIterator_PlannedAlloc(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
resources := &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
task := &structs.Task{
|
||||
Name: "web",
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
},
|
||||
}
|
||||
binp := NewBinPackIterator(ctx, static, resources, false, 0)
|
||||
|
||||
binp := NewBinPackIterator(ctx, static, false, 0)
|
||||
binp.SetTasks([]*structs.Task{task})
|
||||
|
||||
out := collectRanked(binp)
|
||||
if len(out) != 1 {
|
||||
@@ -207,11 +217,16 @@ func TestBinPackIterator_ExistingAlloc(t *testing.T) {
|
||||
}
|
||||
noErr(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2}))
|
||||
|
||||
resources := &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
task := &structs.Task{
|
||||
Name: "web",
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
},
|
||||
}
|
||||
binp := NewBinPackIterator(ctx, static, resources, false, 0)
|
||||
|
||||
binp := NewBinPackIterator(ctx, static, false, 0)
|
||||
binp.SetTasks([]*structs.Task{task})
|
||||
|
||||
out := collectRanked(binp)
|
||||
if len(out) != 1 {
|
||||
@@ -280,11 +295,16 @@ func TestBinPackIterator_ExistingAlloc_PlannedEvict(t *testing.T) {
|
||||
plan := ctx.Plan()
|
||||
plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{alloc1}
|
||||
|
||||
resources := &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
task := &structs.Task{
|
||||
Name: "web",
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1024,
|
||||
MemoryMB: 1024,
|
||||
},
|
||||
}
|
||||
binp := NewBinPackIterator(ctx, static, resources, false, 0)
|
||||
|
||||
binp := NewBinPackIterator(ctx, static, false, 0)
|
||||
binp.SetTasks([]*structs.Task{task})
|
||||
|
||||
out := collectRanked(binp)
|
||||
if len(out) != 2 {
|
||||
|
||||
@@ -76,7 +76,7 @@ func NewGenericStack(batch bool, ctx Context, baseNodes []*structs.Node) *Generi
|
||||
// by a particular task group. Only enable eviction for the service
|
||||
// scheduler as that logic is expensive.
|
||||
evict := !batch
|
||||
s.binPack = NewBinPackIterator(ctx, rankSource, nil, evict, 0)
|
||||
s.binPack = NewBinPackIterator(ctx, rankSource, evict, 0)
|
||||
|
||||
// Apply the job anti-affinity iterator. This is to avoid placing
|
||||
// multiple allocations on the same node for this job. The penalty
|
||||
@@ -149,11 +149,18 @@ func (s *GenericStack) Select(tg *structs.TaskGroup) (*RankedNode, *structs.Reso
|
||||
// Update the parameters of iterators
|
||||
s.taskGroupDrivers.SetDrivers(drivers)
|
||||
s.taskGroupConstraint.SetConstraints(constr)
|
||||
s.binPack.SetResources(size)
|
||||
s.binPack.SetTasks(tg.Tasks)
|
||||
|
||||
// Find the node with the max score
|
||||
option := s.maxScore.Next()
|
||||
|
||||
// Ensure that the task resources were specified
|
||||
if option != nil && len(option.TaskResources) != len(tg.Tasks) {
|
||||
for _, task := range tg.Tasks {
|
||||
option.SetTaskResources(task, task.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
// Store the compute time
|
||||
s.ctx.Metrics().AllocationTime = time.Since(start)
|
||||
return option, size
|
||||
|
||||
@@ -227,6 +227,11 @@ func tasksUpdated(a, b *structs.TaskGroup) bool {
|
||||
if !reflect.DeepEqual(at.Config, bt.Config) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Inspect the network to see if the resource ask is different
|
||||
if !reflect.DeepEqual(at.Resources.Networks, bt.Resources.Networks) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -259,4 +259,10 @@ func TestTasksUpdated(t *testing.T) {
|
||||
if !tasksUpdated(j1.TaskGroups[0], j5.TaskGroups[0]) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
|
||||
j6 := mock.Job()
|
||||
j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = 3
|
||||
if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,34 @@ var offset uint64
|
||||
|
||||
// TestServerConfig is the main server configuration struct.
|
||||
type TestServerConfig struct {
|
||||
HTTPAddr string `json:"http_addr,omitempty"`
|
||||
Bootstrap bool `json:"bootstrap,omitempty"`
|
||||
DataDir string `json:"data_dir,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
DisableCheckpoint bool `json:"disable_update_check"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
Stdout, Stderr io.Writer `json:"-"`
|
||||
Bootstrap bool `json:"bootstrap,omitempty"`
|
||||
DataDir string `json:"data_dir,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
DisableCheckpoint bool `json:"disable_update_check"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
Ports *PortsConfig `json:"ports,omitempty"`
|
||||
Server *ServerConfig `json:"server,omitempty"`
|
||||
Client *ClientConfig `json:"client,omitempty"`
|
||||
DevMode bool `json:"-"`
|
||||
Stdout, Stderr io.Writer `json:"-"`
|
||||
}
|
||||
|
||||
// Ports is used to configure the network ports we use.
|
||||
type PortsConfig struct {
|
||||
HTTP int `json:"http,omitempty"`
|
||||
RPC int `json:"rpc,omitempty"`
|
||||
Serf int `json:"serf,omitempty"`
|
||||
}
|
||||
|
||||
// ServerConfig is used to configure the nomad server.
|
||||
type ServerConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Bootstrap bool `json:"bootstrap"`
|
||||
}
|
||||
|
||||
// ClientConfig is used to configure the client
|
||||
type ClientConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// ServerConfigCallback is a function interface which can be
|
||||
@@ -51,7 +72,18 @@ func defaultServerConfig() *TestServerConfig {
|
||||
DisableCheckpoint: true,
|
||||
Bootstrap: true,
|
||||
LogLevel: "DEBUG",
|
||||
HTTPAddr: fmt.Sprintf("127.0.0.1:%d", 20000+idx),
|
||||
Ports: &PortsConfig{
|
||||
HTTP: 20000 + idx,
|
||||
RPC: 21000 + idx,
|
||||
Serf: 22000 + idx,
|
||||
},
|
||||
Server: &ServerConfig{
|
||||
Enabled: true,
|
||||
Bootstrap: true,
|
||||
},
|
||||
Client: &ClientConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +94,7 @@ type TestServer struct {
|
||||
t *testing.T
|
||||
|
||||
HTTPAddr string
|
||||
SerfAddr string
|
||||
HttpClient *http.Client
|
||||
}
|
||||
|
||||
@@ -110,8 +143,13 @@ func NewTestServer(t *testing.T, cb ServerConfigCallback) *TestServer {
|
||||
stderr = nomadConfig.Stderr
|
||||
}
|
||||
|
||||
args := []string{"agent", "-config", configFile.Name()}
|
||||
if nomadConfig.DevMode {
|
||||
args = append(args, "-dev")
|
||||
}
|
||||
|
||||
// Start the server
|
||||
cmd := exec.Command("nomad", "agent", "-dev", "-config", configFile.Name())
|
||||
cmd := exec.Command("nomad", args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
@@ -126,7 +164,8 @@ func NewTestServer(t *testing.T, cb ServerConfigCallback) *TestServer {
|
||||
PID: cmd.Process.Pid,
|
||||
t: t,
|
||||
|
||||
HTTPAddr: nomadConfig.HTTPAddr,
|
||||
HTTPAddr: fmt.Sprintf("127.0.0.1:%d", nomadConfig.Ports.HTTP),
|
||||
SerfAddr: fmt.Sprintf("127.0.0.1:%d", nomadConfig.Ports.Serf),
|
||||
HttpClient: client,
|
||||
}
|
||||
|
||||
|
||||
2
website/.buildpacks
Normal file
@@ -0,0 +1,2 @@
|
||||
https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
https://github.com/hashicorp/heroku-buildpack-middleman.git
|
||||
3
website/Gemfile
Normal file
@@ -0,0 +1,3 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "middleman-hashicorp", github: "hashicorp/middleman-hashicorp"
|
||||
182
website/Gemfile.lock
Normal file
@@ -0,0 +1,182 @@
|
||||
GIT
|
||||
remote: git://github.com/hashicorp/middleman-hashicorp.git
|
||||
revision: 76f0f284ad44cea0457484ea83467192f02daf87
|
||||
specs:
|
||||
middleman-hashicorp (0.1.0)
|
||||
bootstrap-sass (~> 3.3)
|
||||
builder (~> 3.2)
|
||||
less (~> 2.6)
|
||||
middleman (~> 3.3)
|
||||
middleman-livereload (~> 3.4)
|
||||
middleman-minify-html (~> 3.4)
|
||||
middleman-syntax (~> 2.0)
|
||||
rack-contrib (~> 1.2)
|
||||
rack-protection (~> 1.5)
|
||||
rack-rewrite (~> 1.5)
|
||||
rack-ssl-enforcer (~> 0.2)
|
||||
redcarpet (~> 3.2)
|
||||
therubyracer (~> 0.12)
|
||||
thin (~> 1.6)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (4.1.12)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 1.1)
|
||||
autoprefixer-rails (5.2.1)
|
||||
execjs
|
||||
json
|
||||
bootstrap-sass (3.3.5.1)
|
||||
autoprefixer-rails (>= 5.0.0.1)
|
||||
sass (>= 3.3.0)
|
||||
builder (3.2.2)
|
||||
celluloid (0.16.0)
|
||||
timers (~> 4.0.0)
|
||||
chunky_png (1.3.4)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.9.1.1)
|
||||
commonjs (0.2.7)
|
||||
compass (1.0.3)
|
||||
chunky_png (~> 1.2)
|
||||
compass-core (~> 1.0.2)
|
||||
compass-import-once (~> 1.0.5)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
sass (>= 3.3.13, < 3.5)
|
||||
compass-core (1.0.3)
|
||||
multi_json (~> 1.0)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
compass-import-once (1.0.5)
|
||||
sass (>= 3.2, < 3.5)
|
||||
daemons (1.2.3)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.7)
|
||||
execjs (2.5.2)
|
||||
ffi (1.9.10)
|
||||
git-version-bump (0.15.1)
|
||||
haml (4.0.6)
|
||||
tilt
|
||||
hike (1.2.3)
|
||||
hitimes (1.2.2)
|
||||
hooks (0.4.0)
|
||||
uber (~> 0.0.4)
|
||||
htmlcompressor (0.2.0)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.7.0)
|
||||
json (1.8.3)
|
||||
kramdown (1.8.0)
|
||||
less (2.6.0)
|
||||
commonjs (~> 0.2.7)
|
||||
libv8 (3.16.14.11)
|
||||
listen (2.10.1)
|
||||
celluloid (~> 0.16.0)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
middleman (3.3.12)
|
||||
coffee-script (~> 2.2)
|
||||
compass (>= 1.0.0, < 2.0.0)
|
||||
compass-import-once (= 1.0.5)
|
||||
execjs (~> 2.0)
|
||||
haml (>= 4.0.5)
|
||||
kramdown (~> 1.2)
|
||||
middleman-core (= 3.3.12)
|
||||
middleman-sprockets (>= 3.1.2)
|
||||
sass (>= 3.4.0, < 4.0)
|
||||
uglifier (~> 2.5)
|
||||
middleman-core (3.3.12)
|
||||
activesupport (~> 4.1.0)
|
||||
bundler (~> 1.1)
|
||||
erubis
|
||||
hooks (~> 0.3)
|
||||
i18n (~> 0.7.0)
|
||||
listen (>= 2.7.9, < 3.0)
|
||||
padrino-helpers (~> 0.12.3)
|
||||
rack (>= 1.4.5, < 2.0)
|
||||
rack-test (~> 0.6.2)
|
||||
thor (>= 0.15.2, < 2.0)
|
||||
tilt (~> 1.4.1, < 2.0)
|
||||
middleman-livereload (3.4.2)
|
||||
em-websocket (~> 0.5.1)
|
||||
middleman-core (>= 3.3)
|
||||
rack-livereload (~> 0.3.15)
|
||||
middleman-minify-html (3.4.1)
|
||||
htmlcompressor (~> 0.2.0)
|
||||
middleman-core (>= 3.2)
|
||||
middleman-sprockets (3.4.2)
|
||||
middleman-core (>= 3.3)
|
||||
sprockets (~> 2.12.1)
|
||||
sprockets-helpers (~> 1.1.0)
|
||||
sprockets-sass (~> 1.3.0)
|
||||
middleman-syntax (2.0.0)
|
||||
middleman-core (~> 3.2)
|
||||
rouge (~> 1.0)
|
||||
minitest (5.7.0)
|
||||
multi_json (1.11.2)
|
||||
padrino-helpers (0.12.5)
|
||||
i18n (~> 0.6, >= 0.6.7)
|
||||
padrino-support (= 0.12.5)
|
||||
tilt (~> 1.4.1)
|
||||
padrino-support (0.12.5)
|
||||
activesupport (>= 3.1)
|
||||
rack (1.6.4)
|
||||
rack-contrib (1.3.0)
|
||||
git-version-bump (~> 0.15)
|
||||
rack (~> 1.4)
|
||||
rack-livereload (0.3.16)
|
||||
rack
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-rewrite (1.5.1)
|
||||
rack-ssl-enforcer (0.2.8)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rb-fsevent (0.9.5)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
redcarpet (3.3.2)
|
||||
ref (2.0.0)
|
||||
rouge (1.9.1)
|
||||
sass (3.4.16)
|
||||
sprockets (2.12.4)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sprockets-helpers (1.1.0)
|
||||
sprockets (~> 2.0)
|
||||
sprockets-sass (1.3.1)
|
||||
sprockets (~> 2.0)
|
||||
tilt (~> 1.1)
|
||||
therubyracer (0.12.2)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.6.3)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
timers (4.0.1)
|
||||
hitimes
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.0.13)
|
||||
uglifier (2.7.1)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
middleman-hashicorp!
|
||||
10
website/LICENSE.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Proprietary License
|
||||
|
||||
This license is temporary while a more official one is drafted. However,
|
||||
this should make it clear:
|
||||
|
||||
* The text contents of this website are MPL 2.0 licensed.
|
||||
|
||||
* The design contents of this website are proprietary and may not be reproduced
|
||||
or reused in any way other than to run the Vault website locally. The license
|
||||
for the design is owned solely by HashiCorp, Inc.
|
||||
1
website/Procfile
Normal file
@@ -0,0 +1 @@
|
||||
web: bundle exec thin start -p $PORT
|
||||
24
website/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nomad Website
|
||||
|
||||
This subdirectory contains the entire source for the [Nomad Website](https://nomadproject.io/).
|
||||
This is a [Middleman](http://middlemanapp.com) project, which builds a static
|
||||
site from these source files.
|
||||
|
||||
## Contributions Welcome!
|
||||
|
||||
If you find a typo or you feel like you can improve the HTML, CSS, or
|
||||
JavaScript, we welcome contributions. Feel free to open issues or pull
|
||||
requests like any normal GitHub project, and we'll merge it in.
|
||||
|
||||
## Running the Site Locally
|
||||
|
||||
Running the site locally is simple. Clone this repo and run the following
|
||||
commands:
|
||||
|
||||
```
|
||||
$ bundle
|
||||
$ bundle exec middleman server
|
||||
```
|
||||
|
||||
Then open up `http://localhost:4567`. Note that some URLs you may need to append
|
||||
".html" to make them work (in the navigation).
|
||||
38
website/Vagrantfile
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
$script = <<SCRIPT
|
||||
sudo apt-get -y update
|
||||
|
||||
# RVM/Ruby
|
||||
sudo apt-get -y install curl
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
. ~/.bashrc
|
||||
. ~/.bash_profile
|
||||
rvm install 2.0.0
|
||||
rvm --default use 2.0.0
|
||||
|
||||
# Middleman deps
|
||||
cd /vagrant
|
||||
bundle
|
||||
|
||||
# JS stuff
|
||||
sudo apt-get install -y python-software-properties
|
||||
sudo add-apt-repository -y ppa:chris-lea/node.js
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Get JS deps
|
||||
cd /vagrant/source
|
||||
npm install
|
||||
SCRIPT
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.box = "chef/ubuntu-12.04"
|
||||
config.vm.network "private_network", ip: "33.33.30.10"
|
||||
config.vm.provision "shell", inline: $script, privileged: false
|
||||
config.vm.synced_folder ".", "/vagrant", type: "rsync"
|
||||
end
|
||||
15
website/config.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Configure Middleman
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
set :base_url, "https://www.vaultproject.io/"
|
||||
|
||||
activate :hashicorp do |h|
|
||||
h.version = ENV["VAULT_VERSION"]
|
||||
h.bintray_enabled = ENV["BINTRAY_ENABLED"]
|
||||
h.bintray_repo = "mitchellh/vault"
|
||||
h.bintray_user = "mitchellh"
|
||||
h.bintray_key = ENV["BINTRAY_API_KEY"]
|
||||
|
||||
h.minify_javascript = false
|
||||
end
|
||||
38
website/config.ru
Normal file
@@ -0,0 +1,38 @@
|
||||
require "rack"
|
||||
require "rack/contrib/not_found"
|
||||
require "rack/contrib/response_headers"
|
||||
require "rack/contrib/static_cache"
|
||||
require "rack/contrib/try_static"
|
||||
require "rack/protection"
|
||||
|
||||
# Protect against various bad things
|
||||
use Rack::Protection::JsonCsrf
|
||||
use Rack::Protection::RemoteReferrer
|
||||
use Rack::Protection::HttpOrigin
|
||||
use Rack::Protection::EscapedParams
|
||||
use Rack::Protection::XSSHeader
|
||||
use Rack::Protection::FrameOptions
|
||||
use Rack::Protection::PathTraversal
|
||||
use Rack::Protection::IPSpoofing
|
||||
|
||||
# Properly compress the output if the client can handle it.
|
||||
use Rack::Deflater
|
||||
|
||||
# Set the "forever expire" cache headers for these static assets. Since
|
||||
# we hash the contents of the assets to determine filenames, this is safe
|
||||
# to do.
|
||||
use Rack::StaticCache,
|
||||
:root => "build",
|
||||
:urls => ["/images", "/javascripts", "/stylesheets", "/webfonts"],
|
||||
:duration => 2,
|
||||
:versioning => false
|
||||
|
||||
# Try to find a static file that matches our request, since Middleman
|
||||
# statically generates everything.
|
||||
use Rack::TryStatic,
|
||||
:root => "build",
|
||||
:urls => ["/"],
|
||||
:try => [".html", "index.html", "/index.html"]
|
||||
|
||||
# 404 if we reached this point. Sad times.
|
||||
run Rack::NotFound.new(File.expand_path("../build/404.html", __FILE__))
|
||||
12
website/helpers/sidebar_helpers.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module SidebarHelpers
|
||||
# This helps by setting the "active" class for sidebar nav elements
|
||||
# if the YAML frontmatter matches the expected value.
|
||||
def sidebar_current(expected)
|
||||
current = current_page.data.sidebar_current || ""
|
||||
if current.start_with?(expected)
|
||||
return " class=\"active\""
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
end
|
||||
2
website/source/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Source folder
|
||||
node_modules/
|
||||
5
website/source/404.html.erb
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
noindex: true
|
||||
---
|
||||
|
||||
<h2>Page Not Found</h2>
|
||||
BIN
website/source/assets/images/caret-green.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
website/source/assets/images/caret-green@2x.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
website/source/assets/images/caret-light.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
website/source/assets/images/caret-light@2x.png
Normal file
|
After Width: | Height: | Size: 597 B |
BIN
website/source/assets/images/caret-white.png
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
website/source/assets/images/caret-white@2x.png
Normal file
|
After Width: | Height: | Size: 351 B |
BIN
website/source/assets/images/favicon.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
website/source/assets/images/favicon@2x.png
Normal file
|
After Width: | Height: | Size: 949 B |
BIN
website/source/assets/images/feature-density.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
website/source/assets/images/feature-density@2x.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
website/source/assets/images/feature-deploy.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
website/source/assets/images/feature-deploy@2x.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
website/source/assets/images/feature-healing.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
website/source/assets/images/feature-healing@2x.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
website/source/assets/images/feature-manage.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
website/source/assets/images/feature-manage@2x.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
website/source/assets/images/hashi-logo.png
Normal file
|
After Width: | Height: | Size: 598 B |
BIN
website/source/assets/images/hashi-logo@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
website/source/assets/images/hero-image.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
website/source/assets/images/hero-image@2x.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
website/source/assets/images/hero-logotype.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
website/source/assets/images/hero-logotype@2x.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
website/source/assets/images/icon-docker-outline.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
website/source/assets/images/icon-docker-outline@2x.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
website/source/assets/images/icon-download.png
Normal file
|
After Width: | Height: | Size: 638 B |
BIN
website/source/assets/images/icon-download@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
website/source/assets/images/icon-github.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
website/source/assets/images/icon-github@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
website/source/assets/images/logo-header.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
website/source/assets/images/logo-header@2x.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
website/source/assets/images/partner-amazon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
website/source/assets/images/partner-amazon@2x.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
website/source/assets/images/partner-docker.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
website/source/assets/images/partner-docker@2x.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
website/source/assets/images/partner-engineyard.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
website/source/assets/images/partner-engineyard@2x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
website/source/assets/images/partner-google.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
website/source/assets/images/partner-google@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |