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