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 }