// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package structs import ( "reflect" "testing" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/uuid" psstructs "github.com/hashicorp/nomad/plugins/shared/structs" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" ) // TODO Test func testNode() *Node { return &Node{ ID: uuid.Generate(), Datacenter: "dc1", Name: "foobar", Attributes: map[string]string{ "kernel.name": "linux", "arch": "x86", "version": "0.1.0", "driver.exec": "1", }, NodeResources: &NodeResources{ Cpu: NodeCpuResources{ CpuShares: 4000, }, Memory: NodeMemoryResources{ MemoryMB: 8192, }, Disk: NodeDiskResources{ DiskMB: 100 * 1024, }, Networks: []*NetworkResource{ { Device: "eth0", CIDR: "192.168.0.100/32", IP: "192.168.0.100", MBits: 1000, }, }, }, Links: map[string]string{ "consul": "foobar.dc1", }, Meta: map[string]string{ "pci-dss": "true", }, NodeClass: "linux-medium-pci", NodePool: "dev", Status: NodeStatusReady, } } func TestNode_ComputedClass(t *testing.T) { ci.Parallel(t) require := require.New(t) // Create a node and gets it computed class n := testNode() require.NoError(n.ComputeClass()) require.NotEmpty(n.ComputedClass) old := n.ComputedClass // Compute again to ensure determinism require.NoError(n.ComputeClass()) require.Equal(n.ComputedClass, old) // Modify a field and compute the class again. n.Datacenter = "New DC" require.NoError(n.ComputeClass()) require.NotEqual(n.ComputedClass, old) old = n.ComputedClass // Add a device n.NodeResources.Devices = append(n.NodeResources.Devices, &NodeDeviceResource{ Vendor: "foo", Type: "gpu", Name: "bam", }) require.NoError(n.ComputeClass()) require.NotEqual(n.ComputedClass, old) } func TestNode_ComputedClass_Ignore(t *testing.T) { ci.Parallel(t) require := require.New(t) // Create a node and gets it computed class n := testNode() require.NoError(n.ComputeClass()) require.NotEmpty(n.ComputedClass) old := n.ComputedClass // Modify an ignored field and compute the class again. n.ID = "New ID" require.NoError(n.ComputeClass()) require.NotEmpty(n.ComputedClass) require.Equal(n.ComputedClass, old) } func TestNode_ComputedClass_Device_Attr(t *testing.T) { ci.Parallel(t) require := require.New(t) // Create a node and gets it computed class n := testNode() d := &NodeDeviceResource{ Vendor: "foo", Type: "gpu", Name: "bam", Attributes: map[string]*psstructs.Attribute{ "foo": psstructs.NewBoolAttribute(true), }, } n.NodeResources.Devices = append(n.NodeResources.Devices, d) require.NoError(n.ComputeClass()) require.NotEmpty(n.ComputedClass) old := n.ComputedClass // Update the attributes to be have a unique value d.Attributes["unique.bar"] = psstructs.NewBoolAttribute(false) require.NoError(n.ComputeClass()) require.Equal(n.ComputedClass, old) } func TestNode_ComputedClass_Attr(t *testing.T) { ci.Parallel(t) // Create a node and gets it computed class n := testNode() if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } old := n.ComputedClass // Add a unique addr and compute the class again n.Attributes["unique.foo"] = "bar" if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if old != n.ComputedClass { t.Fatal("ComputeClass() didn't ignore unique attr suffix") } // Modify an attribute and compute the class again. n.Attributes["version"] = "New Version" if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } if old == n.ComputedClass { t.Fatal("ComputeClass() ignored attribute change") } // Remove and attribute and compute the class again. old = n.ComputedClass delete(n.Attributes, "driver.exec") if err := n.ComputeClass(); err != nil { t.Fatalf("ComputedClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } if old == n.ComputedClass { t.Fatalf("ComputedClass() ignored removal of attribute key") } } func TestNode_ComputedClass_Meta(t *testing.T) { ci.Parallel(t) // Create a node and gets it computed class n := testNode() if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } old := n.ComputedClass // Modify a meta key and compute the class again. n.Meta["pci-dss"] = "false" if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } if old == n.ComputedClass { t.Fatal("ComputeClass() ignored meta change") } old = n.ComputedClass // Add a unique meta key and compute the class again. n.Meta["unique.foo"] = "ignore" if err := n.ComputeClass(); err != nil { t.Fatalf("ComputeClass() failed: %v", err) } if n.ComputedClass == "" { t.Fatal("ComputeClass() didn't set computed class") } if old != n.ComputedClass { t.Fatal("ComputeClass() didn't ignore unique meta key") } } func TestNode_ComputedClass_NodePool(t *testing.T) { ci.Parallel(t) // Create a node and get its computed class. n := testNode() err := n.ComputeClass() must.NoError(t, err) must.NotEq(t, "", n.ComputedClass) old := n.ComputedClass // Modify node pool and expect computed class to change. n.NodePool = "prod" err = n.ComputeClass() must.NoError(t, err) must.NotEq(t, "", n.ComputedClass) must.NotEq(t, old, n.ComputedClass) old = n.ComputedClass } func TestNode_EscapedConstraints(t *testing.T) { ci.Parallel(t) // Non-escaped constraints ne1 := &Constraint{ LTarget: "${attr.kernel.name}", RTarget: "linux", Operand: "=", } ne2 := &Constraint{ LTarget: "${meta.key_foo}", RTarget: "linux", Operand: "<", } ne3 := &Constraint{ LTarget: "${node.dc}", RTarget: "test", Operand: "!=", } // Escaped constraints e1 := &Constraint{ LTarget: "${attr.unique.kernel.name}", RTarget: "linux", Operand: "=", } e2 := &Constraint{ LTarget: "${meta.unique.key_foo}", RTarget: "linux", Operand: "<", } e3 := &Constraint{ LTarget: "${unique.node.id}", RTarget: "test", Operand: "!=", } constraints := []*Constraint{ne1, ne2, ne3, e1, e2, e3} expected := []*Constraint{ne1, ne2, ne3} if act := EscapedConstraints(constraints); reflect.DeepEqual(act, expected) { t.Fatalf("EscapedConstraints(%v) returned %v; want %v", constraints, act, expected) } }