github.com/hernad/nomad@v1.6.112/nomad/structs/node_class.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structs
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/mitchellh/hashstructure"
    11  )
    12  
    13  const (
    14  	// NodeUniqueNamespace is a prefix that can be appended to node meta or
    15  	// attribute keys to mark them for exclusion in computed node class.
    16  	NodeUniqueNamespace = "unique."
    17  )
    18  
    19  // UniqueNamespace takes a key and returns the key marked under the unique
    20  // namespace.
    21  func UniqueNamespace(key string) string {
    22  	return fmt.Sprintf("%s%s", NodeUniqueNamespace, key)
    23  }
    24  
    25  // IsUniqueNamespace returns whether the key is under the unique namespace.
    26  func IsUniqueNamespace(key string) bool {
    27  	return strings.HasPrefix(key, NodeUniqueNamespace)
    28  }
    29  
    30  // ComputeClass computes a derived class for the node based on its attributes.
    31  // ComputedClass is a unique id that identifies nodes with a common set of
    32  // attributes and capabilities. Thus, when calculating a node's computed class
    33  // we avoid including any uniquely identifying fields.
    34  func (n *Node) ComputeClass() error {
    35  	hash, err := hashstructure.Hash(n, nil)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	n.ComputedClass = fmt.Sprintf("v1:%d", hash)
    41  	return nil
    42  }
    43  
    44  // HashInclude is used to denylist uniquely identifying node fields from being
    45  // included in the computed node class.
    46  func (n Node) HashInclude(field string, v interface{}) (bool, error) {
    47  	switch field {
    48  	case "Datacenter", "Attributes", "Meta", "NodeClass", "NodePool", "NodeResources":
    49  		return true, nil
    50  	default:
    51  		return false, nil
    52  	}
    53  }
    54  
    55  // HashIncludeMap is used to denylist uniquely identifying node map keys from being
    56  // included in the computed node class.
    57  func (n Node) HashIncludeMap(field string, k, v interface{}) (bool, error) {
    58  	key, ok := k.(string)
    59  	if !ok {
    60  		return false, fmt.Errorf("map key %v not a string", k)
    61  	}
    62  
    63  	switch field {
    64  	case "Meta", "Attributes":
    65  		return !IsUniqueNamespace(key), nil
    66  	default:
    67  		return false, fmt.Errorf("unexpected map field: %v", field)
    68  	}
    69  }
    70  
    71  // HashInclude is used to denylist uniquely identifying node fields from being
    72  // included in the computed node class.
    73  func (n NodeResources) HashInclude(field string, v interface{}) (bool, error) {
    74  	switch field {
    75  	case "Devices":
    76  		return true, nil
    77  	default:
    78  		return false, nil
    79  	}
    80  }
    81  
    82  // HashInclude is used to denylist uniquely identifying node fields from being
    83  // included in the computed node class.
    84  func (n NodeDeviceResource) HashInclude(field string, v interface{}) (bool, error) {
    85  	switch field {
    86  	case "Vendor", "Type", "Name", "Attributes":
    87  		return true, nil
    88  	default:
    89  		return false, nil
    90  	}
    91  }
    92  
    93  // HashIncludeMap is used to denylist uniquely identifying node map keys from being
    94  // included in the computed node class.
    95  func (n NodeDeviceResource) HashIncludeMap(field string, k, v interface{}) (bool, error) {
    96  	key, ok := k.(string)
    97  	if !ok {
    98  		return false, fmt.Errorf("map key %v not a string", k)
    99  	}
   100  
   101  	switch field {
   102  	case "Attributes":
   103  		return !IsUniqueNamespace(key), nil
   104  	default:
   105  		return false, fmt.Errorf("unexpected map field: %v", field)
   106  	}
   107  }
   108  
   109  // EscapedConstraints takes a set of constraints and returns the set that
   110  // escapes computed node classes.
   111  func EscapedConstraints(constraints []*Constraint) []*Constraint {
   112  	var escaped []*Constraint
   113  	for _, c := range constraints {
   114  		if constraintTargetEscapes(c.LTarget) || constraintTargetEscapes(c.RTarget) {
   115  			escaped = append(escaped, c)
   116  		}
   117  	}
   118  
   119  	return escaped
   120  }
   121  
   122  // constraintTargetEscapes returns whether the target of a constraint escapes
   123  // computed node class optimization.
   124  func constraintTargetEscapes(target string) bool {
   125  	switch {
   126  	case strings.HasPrefix(target, "${node.unique."):
   127  		return true
   128  	case strings.HasPrefix(target, "${attr.unique."):
   129  		return true
   130  	case strings.HasPrefix(target, "${meta.unique."):
   131  		return true
   132  	default:
   133  		return false
   134  	}
   135  }