2015-08-13 17:13:11 +00:00
|
|
|
package scheduler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2015-08-13 22:01:02 +00:00
|
|
|
func TestStaticIterator_Reset(t *testing.T) {
|
|
|
|
_, ctx := testContext(t)
|
|
|
|
var nodes []*structs.Node
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
nodes = append(nodes, mock.Node())
|
|
|
|
}
|
|
|
|
static := NewStaticIterator(ctx, nodes)
|
|
|
|
|
|
|
|
for i := 0; i < 6; i++ {
|
|
|
|
static.Reset()
|
|
|
|
for j := 0; j < i; j++ {
|
|
|
|
static.Next()
|
|
|
|
}
|
|
|
|
static.Reset()
|
|
|
|
|
|
|
|
out := collectFeasible(static)
|
|
|
|
if len(out) != len(nodes) {
|
|
|
|
t.Fatalf("out: %#v", out)
|
|
|
|
t.Fatalf("missing nodes %d %#v", i, static)
|
|
|
|
}
|
|
|
|
|
|
|
|
ids := make(map[string]struct{})
|
|
|
|
for _, o := range out {
|
|
|
|
if _, ok := ids[o.ID]; ok {
|
|
|
|
t.Fatalf("duplicate")
|
|
|
|
}
|
|
|
|
ids[o.ID] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-07 18:26:16 +00:00
|
|
|
func TestStaticIterator_SetNodes(t *testing.T) {
|
|
|
|
_, ctx := testContext(t)
|
|
|
|
var nodes []*structs.Node
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
nodes = append(nodes, mock.Node())
|
|
|
|
}
|
|
|
|
static := NewStaticIterator(ctx, nodes)
|
|
|
|
|
|
|
|
newNodes := []*structs.Node{mock.Node()}
|
|
|
|
static.SetNodes(newNodes)
|
|
|
|
|
|
|
|
out := collectFeasible(static)
|
|
|
|
if !reflect.DeepEqual(out, newNodes) {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-13 17:13:11 +00:00
|
|
|
func TestRandomIterator(t *testing.T) {
|
2015-08-13 18:33:58 +00:00
|
|
|
_, ctx := testContext(t)
|
2015-08-13 17:13:11 +00:00
|
|
|
var nodes []*structs.Node
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
nodes = append(nodes, mock.Node())
|
|
|
|
}
|
|
|
|
|
|
|
|
nc := make([]*structs.Node, len(nodes))
|
|
|
|
copy(nc, nodes)
|
|
|
|
rand := NewRandomIterator(ctx, nc)
|
|
|
|
|
2015-08-13 19:02:42 +00:00
|
|
|
out := collectFeasible(rand)
|
2015-08-13 17:13:11 +00:00
|
|
|
if len(out) != len(nodes) {
|
|
|
|
t.Fatalf("missing nodes")
|
|
|
|
}
|
|
|
|
if reflect.DeepEqual(out, nodes) {
|
|
|
|
t.Fatalf("same order")
|
|
|
|
}
|
|
|
|
}
|
2015-08-13 17:19:46 +00:00
|
|
|
|
|
|
|
func TestDriverIterator(t *testing.T) {
|
2015-08-13 18:33:58 +00:00
|
|
|
_, ctx := testContext(t)
|
2015-08-13 17:19:46 +00:00
|
|
|
nodes := []*structs.Node{
|
|
|
|
mock.Node(),
|
|
|
|
mock.Node(),
|
|
|
|
mock.Node(),
|
|
|
|
}
|
|
|
|
static := NewStaticIterator(ctx, nodes)
|
|
|
|
|
|
|
|
nodes[0].Attributes["driver.foo"] = "2"
|
|
|
|
nodes[2].Attributes["driver.foo"] = "2"
|
|
|
|
|
|
|
|
drivers := map[string]struct{}{
|
2015-09-07 02:47:02 +00:00
|
|
|
"exec": struct{}{},
|
|
|
|
"foo": struct{}{},
|
2015-08-13 17:19:46 +00:00
|
|
|
}
|
|
|
|
driver := NewDriverIterator(ctx, static, drivers)
|
|
|
|
|
2015-08-13 19:02:42 +00:00
|
|
|
out := collectFeasible(driver)
|
2015-08-13 17:19:46 +00:00
|
|
|
if len(out) != 2 {
|
|
|
|
t.Fatalf("missing nodes")
|
|
|
|
}
|
|
|
|
if out[0] != nodes[0] || out[1] != nodes[2] {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
|
|
|
}
|
2015-08-13 17:46:30 +00:00
|
|
|
|
|
|
|
func TestConstraintIterator(t *testing.T) {
|
2015-08-13 18:33:58 +00:00
|
|
|
_, ctx := testContext(t)
|
2015-08-13 17:46:30 +00:00
|
|
|
nodes := []*structs.Node{
|
|
|
|
mock.Node(),
|
|
|
|
mock.Node(),
|
|
|
|
mock.Node(),
|
|
|
|
}
|
|
|
|
static := NewStaticIterator(ctx, nodes)
|
|
|
|
|
2015-08-28 08:30:47 +00:00
|
|
|
nodes[0].Attributes["kernel.name"] = "freebsd"
|
2015-08-13 17:46:30 +00:00
|
|
|
nodes[1].Datacenter = "dc2"
|
|
|
|
|
|
|
|
constraints := []*structs.Constraint{
|
|
|
|
&structs.Constraint{
|
|
|
|
Hard: true,
|
|
|
|
Operand: "=",
|
|
|
|
LTarget: "$node.datacenter",
|
|
|
|
RTarget: "dc1",
|
|
|
|
},
|
|
|
|
&structs.Constraint{
|
|
|
|
Hard: true,
|
|
|
|
Operand: "is",
|
2015-08-28 08:30:47 +00:00
|
|
|
LTarget: "$attr.kernel.name",
|
2015-08-13 17:46:30 +00:00
|
|
|
RTarget: "linux",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
constr := NewConstraintIterator(ctx, static, constraints)
|
|
|
|
|
2015-08-13 19:02:42 +00:00
|
|
|
out := collectFeasible(constr)
|
2015-08-13 17:46:30 +00:00
|
|
|
if len(out) != 1 {
|
|
|
|
t.Fatalf("missing nodes")
|
|
|
|
}
|
|
|
|
if out[0] != nodes[2] {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResolveConstraintTarget(t *testing.T) {
|
|
|
|
type tcase struct {
|
|
|
|
target string
|
|
|
|
node *structs.Node
|
|
|
|
val interface{}
|
|
|
|
result bool
|
|
|
|
}
|
|
|
|
node := mock.Node()
|
|
|
|
cases := []tcase{
|
|
|
|
{
|
|
|
|
target: "$node.id",
|
|
|
|
node: node,
|
|
|
|
val: node.ID,
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$node.datacenter",
|
|
|
|
node: node,
|
|
|
|
val: node.Datacenter,
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$node.name",
|
|
|
|
node: node,
|
|
|
|
val: node.Name,
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$node.foo",
|
|
|
|
node: node,
|
|
|
|
result: false,
|
|
|
|
},
|
|
|
|
{
|
2015-08-28 08:30:47 +00:00
|
|
|
target: "$attr.kernel.name",
|
2015-08-13 17:46:30 +00:00
|
|
|
node: node,
|
2015-08-28 08:30:47 +00:00
|
|
|
val: node.Attributes["kernel.name"],
|
2015-08-13 17:46:30 +00:00
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$attr.rand",
|
|
|
|
node: node,
|
|
|
|
result: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$meta.pci-dss",
|
|
|
|
node: node,
|
|
|
|
val: node.Meta["pci-dss"],
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
target: "$meta.rand",
|
|
|
|
node: node,
|
|
|
|
result: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
res, ok := resolveConstraintTarget(tc.target, tc.node)
|
|
|
|
if ok != tc.result {
|
|
|
|
t.Fatalf("TC: %#v, Result: %v %v", tc, res, ok)
|
|
|
|
}
|
|
|
|
if ok && !reflect.DeepEqual(res, tc.val) {
|
|
|
|
t.Fatalf("TC: %#v, Result: %v %v", tc, res, ok)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCheckConstraint(t *testing.T) {
|
|
|
|
type tcase struct {
|
|
|
|
op string
|
|
|
|
lVal, rVal interface{}
|
|
|
|
result bool
|
|
|
|
}
|
|
|
|
cases := []tcase{
|
|
|
|
{
|
|
|
|
op: "=",
|
|
|
|
lVal: "foo", rVal: "foo",
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
op: "is",
|
|
|
|
lVal: "foo", rVal: "foo",
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
op: "==",
|
|
|
|
lVal: "foo", rVal: "foo",
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
op: "!=",
|
|
|
|
lVal: "foo", rVal: "foo",
|
|
|
|
result: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
op: "!=",
|
|
|
|
lVal: "foo", rVal: "bar",
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
op: "not",
|
|
|
|
lVal: "foo", rVal: "bar",
|
|
|
|
result: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
if res := checkConstraint(tc.op, tc.lVal, tc.rVal); res != tc.result {
|
|
|
|
t.Fatalf("TC: %#v, Result: %v", tc, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-08-13 19:02:42 +00:00
|
|
|
|
|
|
|
func collectFeasible(iter FeasibleIterator) (out []*structs.Node) {
|
|
|
|
for {
|
|
|
|
next := iter.Next()
|
|
|
|
if next == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
out = append(out, next)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|