1package structs
2
3import (
4	"reflect"
5	"testing"
6
7	"github.com/hashicorp/nomad/helper/uuid"
8	psstructs "github.com/hashicorp/nomad/plugins/shared/structs"
9	"github.com/stretchr/testify/require"
10)
11
12// TODO Test
13func testNode() *Node {
14	return &Node{
15		ID:         uuid.Generate(),
16		Datacenter: "dc1",
17		Name:       "foobar",
18		Attributes: map[string]string{
19			"kernel.name": "linux",
20			"arch":        "x86",
21			"version":     "0.1.0",
22			"driver.exec": "1",
23		},
24		NodeResources: &NodeResources{
25			Cpu: NodeCpuResources{
26				CpuShares: 4000,
27			},
28			Memory: NodeMemoryResources{
29				MemoryMB: 8192,
30			},
31			Disk: NodeDiskResources{
32				DiskMB: 100 * 1024,
33			},
34			Networks: []*NetworkResource{
35				{
36					Device: "eth0",
37					CIDR:   "192.168.0.100/32",
38					IP:     "192.168.0.100",
39					MBits:  1000,
40				},
41			},
42		},
43		Links: map[string]string{
44			"consul": "foobar.dc1",
45		},
46		Meta: map[string]string{
47			"pci-dss": "true",
48		},
49		NodeClass: "linux-medium-pci",
50		Status:    NodeStatusReady,
51	}
52}
53
54func TestNode_ComputedClass(t *testing.T) {
55	require := require.New(t)
56
57	// Create a node and gets it computed class
58	n := testNode()
59	require.NoError(n.ComputeClass())
60	require.NotEmpty(n.ComputedClass)
61	old := n.ComputedClass
62
63	// Compute again to ensure determinism
64	require.NoError(n.ComputeClass())
65	require.Equal(n.ComputedClass, old)
66
67	// Modify a field and compute the class again.
68	n.Datacenter = "New DC"
69	require.NoError(n.ComputeClass())
70	require.NotEqual(n.ComputedClass, old)
71	old = n.ComputedClass
72
73	// Add a device
74	n.NodeResources.Devices = append(n.NodeResources.Devices, &NodeDeviceResource{
75		Vendor: "foo",
76		Type:   "gpu",
77		Name:   "bam",
78	})
79	require.NoError(n.ComputeClass())
80	require.NotEqual(n.ComputedClass, old)
81}
82
83func TestNode_ComputedClass_Ignore(t *testing.T) {
84	require := require.New(t)
85
86	// Create a node and gets it computed class
87	n := testNode()
88	require.NoError(n.ComputeClass())
89	require.NotEmpty(n.ComputedClass)
90	old := n.ComputedClass
91
92	// Modify an ignored field and compute the class again.
93	n.ID = "New ID"
94	require.NoError(n.ComputeClass())
95	require.NotEmpty(n.ComputedClass)
96	require.Equal(n.ComputedClass, old)
97
98}
99
100func TestNode_ComputedClass_Device_Attr(t *testing.T) {
101	require := require.New(t)
102
103	// Create a node and gets it computed class
104	n := testNode()
105	d := &NodeDeviceResource{
106		Vendor: "foo",
107		Type:   "gpu",
108		Name:   "bam",
109		Attributes: map[string]*psstructs.Attribute{
110			"foo": psstructs.NewBoolAttribute(true),
111		},
112	}
113	n.NodeResources.Devices = append(n.NodeResources.Devices, d)
114	require.NoError(n.ComputeClass())
115	require.NotEmpty(n.ComputedClass)
116	old := n.ComputedClass
117
118	// Update the attributes to be have a unique value
119	d.Attributes["unique.bar"] = psstructs.NewBoolAttribute(false)
120	require.NoError(n.ComputeClass())
121	require.Equal(n.ComputedClass, old)
122}
123
124func TestNode_ComputedClass_Attr(t *testing.T) {
125	// Create a node and gets it computed class
126	n := testNode()
127	if err := n.ComputeClass(); err != nil {
128		t.Fatalf("ComputeClass() failed: %v", err)
129	}
130	if n.ComputedClass == "" {
131		t.Fatal("ComputeClass() didn't set computed class")
132	}
133	old := n.ComputedClass
134
135	// Add a unique addr and compute the class again
136	n.Attributes["unique.foo"] = "bar"
137	if err := n.ComputeClass(); err != nil {
138		t.Fatalf("ComputeClass() failed: %v", err)
139	}
140	if old != n.ComputedClass {
141		t.Fatal("ComputeClass() didn't ignore unique attr suffix")
142	}
143
144	// Modify an attribute and compute the class again.
145	n.Attributes["version"] = "New Version"
146	if err := n.ComputeClass(); err != nil {
147		t.Fatalf("ComputeClass() failed: %v", err)
148	}
149	if n.ComputedClass == "" {
150		t.Fatal("ComputeClass() didn't set computed class")
151	}
152	if old == n.ComputedClass {
153		t.Fatal("ComputeClass() ignored attribute change")
154	}
155
156	// Remove and attribute and compute the class again.
157	old = n.ComputedClass
158	delete(n.Attributes, "driver.exec")
159	if err := n.ComputeClass(); err != nil {
160		t.Fatalf("ComputedClass() failed: %v", err)
161	}
162	if n.ComputedClass == "" {
163		t.Fatal("ComputeClass() didn't set computed class")
164	}
165	if old == n.ComputedClass {
166		t.Fatalf("ComputedClass() ignored removal of attribute key")
167	}
168}
169
170func TestNode_ComputedClass_Meta(t *testing.T) {
171	// Create a node and gets it computed class
172	n := testNode()
173	if err := n.ComputeClass(); err != nil {
174		t.Fatalf("ComputeClass() failed: %v", err)
175	}
176	if n.ComputedClass == "" {
177		t.Fatal("ComputeClass() didn't set computed class")
178	}
179	old := n.ComputedClass
180
181	// Modify a meta key and compute the class again.
182	n.Meta["pci-dss"] = "false"
183	if err := n.ComputeClass(); err != nil {
184		t.Fatalf("ComputeClass() failed: %v", err)
185	}
186	if n.ComputedClass == "" {
187		t.Fatal("ComputeClass() didn't set computed class")
188	}
189	if old == n.ComputedClass {
190		t.Fatal("ComputeClass() ignored meta change")
191	}
192	old = n.ComputedClass
193
194	// Add a unique meta key and compute the class again.
195	n.Meta["unique.foo"] = "ignore"
196	if err := n.ComputeClass(); err != nil {
197		t.Fatalf("ComputeClass() failed: %v", err)
198	}
199	if n.ComputedClass == "" {
200		t.Fatal("ComputeClass() didn't set computed class")
201	}
202	if old != n.ComputedClass {
203		t.Fatal("ComputeClass() didn't ignore unique meta key")
204	}
205}
206
207func TestNode_EscapedConstraints(t *testing.T) {
208	// Non-escaped constraints
209	ne1 := &Constraint{
210		LTarget: "${attr.kernel.name}",
211		RTarget: "linux",
212		Operand: "=",
213	}
214	ne2 := &Constraint{
215		LTarget: "${meta.key_foo}",
216		RTarget: "linux",
217		Operand: "<",
218	}
219	ne3 := &Constraint{
220		LTarget: "${node.dc}",
221		RTarget: "test",
222		Operand: "!=",
223	}
224
225	// Escaped constraints
226	e1 := &Constraint{
227		LTarget: "${attr.unique.kernel.name}",
228		RTarget: "linux",
229		Operand: "=",
230	}
231	e2 := &Constraint{
232		LTarget: "${meta.unique.key_foo}",
233		RTarget: "linux",
234		Operand: "<",
235	}
236	e3 := &Constraint{
237		LTarget: "${unique.node.id}",
238		RTarget: "test",
239		Operand: "!=",
240	}
241	constraints := []*Constraint{ne1, ne2, ne3, e1, e2, e3}
242	expected := []*Constraint{ne1, ne2, ne3}
243	if act := EscapedConstraints(constraints); reflect.DeepEqual(act, expected) {
244		t.Fatalf("EscapedConstraints(%v) returned %v; want %v", constraints, act, expected)
245	}
246}
247