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  }