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 ...
This commit is contained in:
commit
676a27da10
13
Makefile
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
91
README.md
|
@ -1,2 +1,89 @@
|
|||
# nomad
|
||||
Where the wild bits roam
|
||||
Nomad [![Build Status](https://travis-ci.org/hashicorp/nomad.svg)](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](https://raw.githubusercontent.com/hashicorp/nomad/master/website/source/assets/images/logo-header%402x.png?token=AAkIoLO_y1g3wgHMr3QO-559BN22rN0kks5V_2HpwA%3D%3D)
|
||||
|
||||
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
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
https://github.com/hashicorp/heroku-buildpack-middleman.git
|
|
@ -0,0 +1,3 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "middleman-hashicorp", github: "hashicorp/middleman-hashicorp"
|
|
@ -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!
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
web: bundle exec thin start -p $PORT
|
|
@ -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).
|
|
@ -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
|
|
@ -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
|
|
@ -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__))
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
# Source folder
|
||||
node_modules/
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
noindex: true
|
||||
---
|
||||
|
||||
<h2>Page Not Found</h2>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue